Merge btcjson repo into btcjson directory.
This commit is contained in:
commit
45f7a514b6
49 changed files with 23100 additions and 0 deletions
16
btcjson/CONTRIBUTORS
Normal file
16
btcjson/CONTRIBUTORS
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# This is the list of people who have contributed code to the repository.
|
||||||
|
#
|
||||||
|
# Names should be added to this file only after verifying that the individual
|
||||||
|
# or the individual's organization has agreed to the LICENSE.
|
||||||
|
#
|
||||||
|
# Names should be added to this file like so:
|
||||||
|
# Name <email address>
|
||||||
|
|
||||||
|
John C. Vernaleo <jcv@conformal.com>
|
||||||
|
Dave Collins <davec@conformal.com>
|
||||||
|
Owain G. Ainsworth <oga@conformal.com>
|
||||||
|
David Hill <dhill@conformal.com>
|
||||||
|
Josh Rickmar <jrick@conformal.com>
|
||||||
|
Andreas Metsälä <andreas.metsala@gmail.com>
|
||||||
|
Francis Lam <flam@alum.mit.edu>
|
||||||
|
Geert-Johan Riemer <geertjohan.riemer@gmail.com>
|
104
btcjson/README.md
Normal file
104
btcjson/README.md
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
btcjson
|
||||||
|
=======
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/btcsuite/btcd.png?branch=master)]
|
||||||
|
(https://travis-ci.org/btcsuite/btcd) [![ISC License]
|
||||||
|
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
|
||||||
|
|
||||||
|
Package btcjson implements concrete types for marshalling to and from the
|
||||||
|
bitcoin JSON-RPC API. A comprehensive suite of tests is provided to ensure
|
||||||
|
proper functionality. Package btcjson is licensed under the copyfree ISC
|
||||||
|
license.
|
||||||
|
|
||||||
|
Although this package was primarily written for btcd, it has intentionally been
|
||||||
|
designed so it can be used as a standalone package for any projects needing to
|
||||||
|
marshal to and from bitcoin JSON-RPC requests and responses.
|
||||||
|
|
||||||
|
Note that although it's possible to use this package directly to implement an
|
||||||
|
RPC client, it is not recommended since it is only intended as an infrastructure
|
||||||
|
package. Instead, RPC clients should use the
|
||||||
|
[btcrpcclient](https://github.com/btcsuite/btcrpcclient) package which provides
|
||||||
|
a full blown RPC client with many features such as automatic connection
|
||||||
|
management, websocket support, automatic notification re-registration on
|
||||||
|
reconnect, and conversion from the raw underlying RPC types (strings, floats,
|
||||||
|
ints, etc) to higher-level types with many nice and useful properties.
|
||||||
|
|
||||||
|
## JSON RPC
|
||||||
|
|
||||||
|
Bitcoin provides an extensive API call list to control the chain and wallet
|
||||||
|
servers through JSON-RPC. These can be used to get information from the server
|
||||||
|
or to cause the server to perform some action.
|
||||||
|
|
||||||
|
The general form of the commands are:
|
||||||
|
|
||||||
|
```JSON
|
||||||
|
{"jsonrpc": "1.0", "id":"test", "method": "getinfo", "params": []}
|
||||||
|
```
|
||||||
|
|
||||||
|
btcjson provides code to easily create these commands from go (as some of the
|
||||||
|
commands can be fairly complex), to send the commands to a running bitcoin RPC
|
||||||
|
server, and to handle the replies (putting them in useful Go data structures).
|
||||||
|
|
||||||
|
## Sample Use
|
||||||
|
|
||||||
|
```Go
|
||||||
|
// Create a new command.
|
||||||
|
cmd, err := btcjson.NewGetBlockCountCmd()
|
||||||
|
if err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the command to a JSON-RPC formatted byte slice.
|
||||||
|
marshalled, err := btcjson.MarshalCmd(id, cmd)
|
||||||
|
if err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point marshalled contains the raw bytes that are ready to send
|
||||||
|
// to the RPC server to issue the command.
|
||||||
|
fmt.Printf("%s\n", marshalled)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
|
||||||
|
(http://godoc.org/github.com/btcsuite/btcd/btcjson)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the GoDoc site
|
||||||
|
[here](http://godoc.org/github.com/btcsuite/btcd/btcjson).
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/btcsuite/btcd/btcjson
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get github.com/btcsuite/btcd/btcjson
|
||||||
|
```
|
||||||
|
|
||||||
|
## GPG Verification Key
|
||||||
|
|
||||||
|
All official release tags are signed by Conformal so users can ensure the code
|
||||||
|
has not been tampered with and is coming from Conformal. To verify the
|
||||||
|
signature perform the following:
|
||||||
|
|
||||||
|
- Download the public key from the Conformal website at
|
||||||
|
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
|
||||||
|
|
||||||
|
- Import the public key into your GPG keyring:
|
||||||
|
```bash
|
||||||
|
gpg --import GIT-GPG-KEY-conformal.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
- Verify the release tag with the following command where `TAG_NAME` is a
|
||||||
|
placeholder for the specific tag:
|
||||||
|
```bash
|
||||||
|
git tag -v TAG_NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Package btcjson is licensed under the [copyfree](http://copyfree.org) ISC
|
||||||
|
License.
|
826
btcjson/cmdhelp.go
Normal file
826
btcjson/cmdhelp.go
Normal file
|
@ -0,0 +1,826 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultHelpStrings contains the help text for all commands that are supported
|
||||||
|
// by btcjson by default.
|
||||||
|
var defaultHelpStrings = map[string]string{
|
||||||
|
"addmultisigaddress": `addmultisigaddress nrequired ["key",...] ("account" )
|
||||||
|
Add a multisignature address to the wallet where 'nrequired' signatures are
|
||||||
|
required for spending. Each key is an address of publickey. Optionally, account
|
||||||
|
may be provided to assign the address to that account.`,
|
||||||
|
|
||||||
|
"addnode": `addnode "node" "{add|remove|onetry}"
|
||||||
|
Add or remove a node from the list of hosts we connect to. If mode is "onetry"
|
||||||
|
then the host will be connected to once only, otherwise the node will be retried
|
||||||
|
upon disconnection.`,
|
||||||
|
|
||||||
|
"backupwallet": `backupwallet "destination"
|
||||||
|
Safely copies the wallet file to the destination provided, either a directory or
|
||||||
|
a filename.`,
|
||||||
|
|
||||||
|
"createmultisig": `createmultisig nrequired ["key", ...]
|
||||||
|
Creates a multi-signature address with m keys where "nrequired" signatures are
|
||||||
|
required from those m. A JSON object is returned containing the address and
|
||||||
|
redemption script:
|
||||||
|
{
|
||||||
|
"address":"address", # the value of the new address.
|
||||||
|
redeemScript":"script" # The stringified hex-encoded redemption script.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"createrawtransaction": `createrawtransaction [{"txid":"id", "vout":n},...] {"address":amount,...}
|
||||||
|
Creates a transaction spending the given inputs and outputting to the
|
||||||
|
given addresses. The return is a hex encoded string of the raw
|
||||||
|
transaction with *unsigned* inputs. The transaction is not stored in any
|
||||||
|
wallet.`,
|
||||||
|
|
||||||
|
// TODO(oga) this should be external since it is nonstandard.
|
||||||
|
"debuglevel": `debuglevel "levelspec"
|
||||||
|
Dynamically changes the debug logging level. Levelspec must be either a debug
|
||||||
|
level, one of the following:
|
||||||
|
trace,
|
||||||
|
debug,
|
||||||
|
info,
|
||||||
|
warn,
|
||||||
|
error,
|
||||||
|
critical.
|
||||||
|
Alternatively levelspec may be a specification of the form:
|
||||||
|
<subsystem>=<level>,<subsystem2>=<level2>
|
||||||
|
Where the valid subsystem names are:
|
||||||
|
AMGR,
|
||||||
|
ADXR,
|
||||||
|
BCDB,
|
||||||
|
BMGR,
|
||||||
|
BTCD,
|
||||||
|
CHAN,
|
||||||
|
DISC,
|
||||||
|
PEER,
|
||||||
|
RPCS,
|
||||||
|
SCRP,
|
||||||
|
SRVR,
|
||||||
|
TXMP.
|
||||||
|
Finally the keyword "show" will return a list of the available subsystems.
|
||||||
|
The command returns a string which will be "Done." if the command was sucessful,
|
||||||
|
or the list of subsystems if "show" was specified.`,
|
||||||
|
|
||||||
|
"decoderawtransaction": `decoderawtransaction "hexstring"
|
||||||
|
Decodes the seralized, hex-encoded transaction in hexstring and returns a JSON
|
||||||
|
object representing it:
|
||||||
|
{
|
||||||
|
"hex":"hex", # String of the hex encoded transaction provided.
|
||||||
|
"txid":"id", # The sha id of the transaction as a hex string
|
||||||
|
"version":n, # The version of the transaction as a number.
|
||||||
|
"locktime":t, # Locktime of the tx (number).
|
||||||
|
"vin": [ # Array of objects for inputs.
|
||||||
|
{
|
||||||
|
"txid":"id", # Txid that is spent.
|
||||||
|
"vout":n, # Output number.
|
||||||
|
"scriptSig": {
|
||||||
|
"asm":"asm", # Disasembled script as a string.
|
||||||
|
"hex":"hex", # Hex string of script.
|
||||||
|
},
|
||||||
|
"sequence":n, # Sequence number of script.
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vout": [ # Array of objects for outputs.
|
||||||
|
{
|
||||||
|
"value":x.xxx, # Value in BTC.
|
||||||
|
"n":n, # Numeric index.
|
||||||
|
"scriptPubkey": { # Object representing script.
|
||||||
|
"asm":"asm", # Disassembled script as string.
|
||||||
|
"hex":"hex", # Hex string of script.
|
||||||
|
"reqSigs":n, # Number of required signatures.
|
||||||
|
"type":"type" # Type as string, e.g. pubkeyhash.
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"blockhash":"hash", # The block hash. as a string.
|
||||||
|
"confirmations":n # Number of confirmations in blockchain.
|
||||||
|
"time":t, # Transaction time in seconds since the epoch.
|
||||||
|
"blocktime":t, # Block time in seconds since the epoch.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"decodescript": `decodescript "hex"
|
||||||
|
Decodes the hex encoded script passed as a string and returns a JSON object:
|
||||||
|
{
|
||||||
|
"asm":"asm", # disassembled string of the script.
|
||||||
|
"hex":"hex", # hex string of the script.
|
||||||
|
"type":"type", # type of script as a string.
|
||||||
|
"reqSigs":n, # number of required signatures.
|
||||||
|
"addresses": [ # JSON array of address strings.
|
||||||
|
"address", # bitcoin address as a string.
|
||||||
|
],
|
||||||
|
"p2sh","address" # script address as a string.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"dumpprivkey": `dumpprivkey "bitcoinaddress"
|
||||||
|
Returns the private key corresponding to the provided address in a format
|
||||||
|
compatible with importprivkey.`,
|
||||||
|
|
||||||
|
"dumpwallet": `dumpwallet "filename"
|
||||||
|
Dumps all wallet keys into "filename" in a human readable format.`,
|
||||||
|
|
||||||
|
"encryptwallet": `encryptwallet "passphrase"
|
||||||
|
Encrypts the wallet with "passphrase". This command is for the initial
|
||||||
|
encryption of an otherwise unencrypted wallet, changing a passphrase
|
||||||
|
should use walletpassphrasechange.`,
|
||||||
|
|
||||||
|
"estimatefee": `estimatefee "numblocks"
|
||||||
|
Estimates the approximate fee per kilobyte needed for a transaction to
|
||||||
|
get confirmed within 'numblocks' blocks.`,
|
||||||
|
|
||||||
|
"estimatepriority": `estimatepriority "numblocks"
|
||||||
|
Estimates the approximate priority a zero-fee transaction needs to get
|
||||||
|
confirmed within 'numblocks' blocks.`,
|
||||||
|
|
||||||
|
"getaccount": `getaccount "address"
|
||||||
|
Returns the account associated with the given "address" as a string.`,
|
||||||
|
|
||||||
|
"getaccountaddress": `getaccountaddress "account"
|
||||||
|
Returns the current address used for receiving payments in this given account.`,
|
||||||
|
|
||||||
|
"getaddednodeinfo": `getaddednodeinfo dns ( "node" )
|
||||||
|
Returns a list of JSON objects with information about the local list of
|
||||||
|
permanent nodes. If dns is false, only a list of permanent nodes will
|
||||||
|
be provided, otherwise connected information will also be provided. If
|
||||||
|
node is not provided then all nodes will be detailed. The JSON return
|
||||||
|
format is as follows:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"addednode":"1.2.3.4", # node ip address as a string
|
||||||
|
"connected":true|false # boolean connectionstate.
|
||||||
|
"addresses": [
|
||||||
|
"address":"1.2.3.4:5678" # Bitcoin server host and port as a string.
|
||||||
|
"connected":"inbound", # The string "inbound" or "outbound".
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]`,
|
||||||
|
|
||||||
|
"getaddressesbyaccount": `getaddressesbyaccount "account"
|
||||||
|
Returns the list of addresses for the given account:
|
||||||
|
[
|
||||||
|
"address", # Bitcoin address associated with the given account.
|
||||||
|
...
|
||||||
|
]`,
|
||||||
|
|
||||||
|
"getbalance": `getbalance ("account" "minconf")
|
||||||
|
Returns the balance for an account. If "account" is not specified this is the
|
||||||
|
total balance for the server. if "minconf" is provided then only transactions
|
||||||
|
with at least "minconf" confirmations will be counted for the balance. The
|
||||||
|
result is a JSON numeric type.`,
|
||||||
|
|
||||||
|
"getbestblockhash": `getbestblockhash
|
||||||
|
Returns the hash of the last block in the longest blockchain known to
|
||||||
|
the server as a hex encoded string.`,
|
||||||
|
|
||||||
|
"getblock": `getblock "hash" ( verbose verbosetx=false)
|
||||||
|
Returns data about the block with hash "hash". If verbose is false a
|
||||||
|
string of hex-encoded data for the block is returned. If verbose is true
|
||||||
|
a JSON object is provided with the following:
|
||||||
|
{
|
||||||
|
"hash":"hash", # The block hash (same as argument).
|
||||||
|
"confirmations":n, # Number of confirmations as numeric.
|
||||||
|
"size":n, # Block size as numeric.
|
||||||
|
"height":n, # Block height as numeric.
|
||||||
|
"version":n, # Block version as numeric.
|
||||||
|
"merkelroot":"...", # The merkle root of the block.
|
||||||
|
"tx" : [ # the transactions in the block as an array of strings.
|
||||||
|
"transactionid",
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"time":t, # The block time in seconds since the epoch.
|
||||||
|
"nonce":n, # The nonce of the block as a number.
|
||||||
|
"bits":"1d00ffff", # the compact representation of the difficulty as bits.
|
||||||
|
"difficulty":n, # the difficulty of the block as a number.
|
||||||
|
"previousblockhash":"hash", # hash of the previous block as a string.
|
||||||
|
"nextblockhash":""hash", # hash of the next block as a string.
|
||||||
|
}
|
||||||
|
If "verbosetx" is true, the returned object will contain a "rawtx" member
|
||||||
|
instead of the "tx" member; It will contain objects representing the
|
||||||
|
transactions in the block in format used by getrawtransaction.
|
||||||
|
Please note that verbosetx is a btcd/btcjson extension.`,
|
||||||
|
|
||||||
|
"getblockcount": `getblockcount
|
||||||
|
Returns a numeric for the number of blocks in the longest block chain.`,
|
||||||
|
|
||||||
|
"getblockhash": `getblockhash index
|
||||||
|
Returns the hash of the block (as a string) at the given index in the
|
||||||
|
current blockchain.`,
|
||||||
|
|
||||||
|
"getblocktemplate": `getblocktemplate ( jsonrequestobject )
|
||||||
|
Returns a block template for external mining purposes. The optional
|
||||||
|
request object follows the following format:
|
||||||
|
{
|
||||||
|
"mode":"template" # Optional, "template" or omitted.
|
||||||
|
"capabilities": [ # List of strings, optional.
|
||||||
|
"support", # Client side features supported. one of:
|
||||||
|
... # "longpoll", "coinbasetxn", "coinbasevalue",
|
||||||
|
... # "proposal", "serverlist", "workid".
|
||||||
|
]
|
||||||
|
}
|
||||||
|
The result object is of the following format:
|
||||||
|
{
|
||||||
|
"version":n, # Numeric block version.
|
||||||
|
"previousblockhash":"string" # Hash of the current tip of the blocktrain.
|
||||||
|
"transactions":[
|
||||||
|
"data" # String of hex-encoded serialized transaction.
|
||||||
|
"hash" # Hex encoded hash of the tx.
|
||||||
|
"depends": [ # Array of numbers representing the transactions
|
||||||
|
n, # that must be included in the final block if
|
||||||
|
..., # this one is. A 1 based index into the
|
||||||
|
..., # transactions list.
|
||||||
|
]
|
||||||
|
"fee":n # Numeric transaction fee in satoshi. This is calculated by the diffrence between the sum of inputs and outputs. For coinbase transaction this is a negative number of total collected block fees. If not present fee is unknown; clients must not assume that there is no fee in this case.
|
||||||
|
"sigops":n # Number of total signature operations calculated for purposes of block limits. If not present the count is unknown but clients must not assume it is zero.
|
||||||
|
"required":true|false # If provided and true this transaction *must* be in the final block.
|
||||||
|
],
|
||||||
|
"coinbaseaux": { # Object of data to be included in coinbase's scriptSig.
|
||||||
|
"flags":"flags" # String of flags.
|
||||||
|
}
|
||||||
|
"coinbasevalue" # Numeric value in satoshi for maximum allowable input to coinbase transaction, including transaction fees and mining aware.
|
||||||
|
"coinbasetxn":{} # Object contining information for coinbase transaction.
|
||||||
|
"target":"target", # The hash target as a string.
|
||||||
|
"mintime":t, # Minimum timestamp appropriate for next block in seconds since the epoch.
|
||||||
|
"mutable":[ # Array of ways the template may be modified.
|
||||||
|
"value" # e.g. "time", "transactions", "prevblock", etc
|
||||||
|
]
|
||||||
|
"noncerange":"00000000ffffffff" # Wtring representing the range of valid nonces.
|
||||||
|
"sigopliit" # Numeric limit for max sigops in block.
|
||||||
|
"sizelimit" # Numeric limit of block size.
|
||||||
|
"curtime":t # Current timestamp in seconds since the epoch.
|
||||||
|
"bits":"xxx", # Compressed target for next block as string.
|
||||||
|
"height":n, # Numeric height of the next block.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"getconnectioncount": `getconnectioncount
|
||||||
|
Returns the number of connections to other nodes currently active as a JSON
|
||||||
|
number.`,
|
||||||
|
|
||||||
|
"getdifficulty": `getdifficulty
|
||||||
|
Returns the proof-of-work difficulty as a JSON number. The result is a
|
||||||
|
multiple of the minimum difficulty.`,
|
||||||
|
|
||||||
|
"getgenerate": `getgenerate
|
||||||
|
Returns JSON boolean whether or not the server is set to "mine" coins or not.`,
|
||||||
|
|
||||||
|
"gethashespersec": `gethashespersec
|
||||||
|
Returns a JSON number representing a recent measurement of hashes-per-second
|
||||||
|
during mining.`,
|
||||||
|
|
||||||
|
"getinfo": `getinfo
|
||||||
|
Returns a JSON object containing state information about the service:
|
||||||
|
{
|
||||||
|
"version":n, # Numeric server version.
|
||||||
|
"protocolversion":n, # Numeric protocol version.
|
||||||
|
"walletversion":n, # Numeric wallet version.
|
||||||
|
"balance":n, # Total balance in the wallet as a number.
|
||||||
|
"blocks":n, # Numeric detailing current number of blocks.
|
||||||
|
"timeoffset":n, # Numeric server time offset.
|
||||||
|
"proxy":"host:port" # Optional string detailing the proxy in use.
|
||||||
|
"difficulty":n, # Current blockchain difficulty as a number.
|
||||||
|
"testnet":true|false # Boolean if the server is testnet.
|
||||||
|
"keypoololdest":t, # Oldest timstamp for pre generated keys. (in seconds since the epoch).
|
||||||
|
"keypoolsize":n, # Numeric size of the wallet keypool.
|
||||||
|
"paytxfee":n, # Numeric transaction fee that has been set.
|
||||||
|
"unlocked_until":t, # Numeric time the wallet is unlocked for in seconds since epoch.
|
||||||
|
"errors":"..." # Any error messages as a string.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"getmininginfo": `getmininginfo
|
||||||
|
Returns a JSON object containing information related to mining:
|
||||||
|
{
|
||||||
|
"blocks":n, # Numeric current block
|
||||||
|
"currentblocksize":n, # Numeric last block size.
|
||||||
|
currentblocktx":n, # Numeric last block transaction
|
||||||
|
"difficulty":n, # Numeric current difficulty.
|
||||||
|
"errors":"...", # Current error string.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"getnettotals": `getnettotals
|
||||||
|
Returns JSON object containing network traffic statistics:
|
||||||
|
{
|
||||||
|
"totalbytesrecv":n, # Numeric total bytes received.
|
||||||
|
"totalbytessent":n, # Numeric total bytes sent.
|
||||||
|
"timemilis",t, # Total numeric of milliseconds since epoch.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"getnetworkhashps": `getnetworkhashps ( blocks=120 height=-1 )
|
||||||
|
Returns the estimated network hash rate per second based on the last
|
||||||
|
"blocks" blocks. If "blocks" is -1 then the number of blocks since the
|
||||||
|
last difficulty change will be used. If "height" is set then the
|
||||||
|
calculation will be carried out for the given block height instead of
|
||||||
|
the block tip. A JSON number is returned with the hashes per second
|
||||||
|
estimate.`,
|
||||||
|
|
||||||
|
"getnewaddress": `getnewaddress ( "account" )
|
||||||
|
Returns a string for a new Bitcoin address for receiving payments. In the case
|
||||||
|
that "account" is specified then the address will be for "account", else the
|
||||||
|
default account will be used.`,
|
||||||
|
|
||||||
|
"getpeerinfo": `getpeerinfo
|
||||||
|
Returns a list of JSON objects containing information about each connected
|
||||||
|
network node. The objects have the following format:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"addr":"host:port" # IP and port of the peer as a string.
|
||||||
|
"addrlocal":"ip:port" # Local address as a string.
|
||||||
|
"services":"00000001", # Services bitmask as a string.
|
||||||
|
"lastsend":t, # Time in seconds since epoch since last send.
|
||||||
|
"lastrecv":t, # Time in seconds since epoch since last received message.
|
||||||
|
"bytessent":n, # Total number of bytes sent.
|
||||||
|
"bytesrecv":n, # Total number of bytes received
|
||||||
|
"conntime":t, # Connection time in seconds since epoc.
|
||||||
|
"pingtime":n, # Ping time
|
||||||
|
"pingwait":n, # Ping wait.
|
||||||
|
"version":n, # The numeric peer version.
|
||||||
|
"subver":"/btcd:0.1/" # The peer useragent string.
|
||||||
|
"inbound":true|false, # True or false whether peer is inbound.
|
||||||
|
"startingheight":n, # Numeric block heght of peer at connect time.
|
||||||
|
"banscore":n, # The numeric ban score.
|
||||||
|
"syncnode":true|false, # Boolean if the peer is the current sync node.
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
"getrawchangeaddress": `getrawchangeaddress
|
||||||
|
Returns a string containing a new Bitcoin addres for receiving change.
|
||||||
|
This rpc call is for use with raw transactions only.`,
|
||||||
|
|
||||||
|
"getrawmempool": `getrawmempool ( verbose )
|
||||||
|
Returns the contents of the transaction memory pool as a JSON array. If
|
||||||
|
verbose is false just the transaction ids will be returned as strings.
|
||||||
|
If it is true then objects in the following format will be returned:
|
||||||
|
[
|
||||||
|
"transactionid": {
|
||||||
|
"size":n, # Numeric transaction size in bytes.
|
||||||
|
"fee":n, # Numeric transqaction fee in btc.
|
||||||
|
"time":t, # Time transaction entered pool in seconds since the epoch.
|
||||||
|
"height":n, # Numeric block height when the transaction entered pool.
|
||||||
|
"startingpriority:n, # Numeric transaction priority when it entered the pool.
|
||||||
|
"currentpriority":n, # Numeric transaction priority.
|
||||||
|
"depends":[ # Unconfirmed transactions used as inputs for this one. As an array of strings.
|
||||||
|
"transactionid", # Parent transaction id.
|
||||||
|
]
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]`,
|
||||||
|
|
||||||
|
"getrawtransaction": `getrawtransaction "txid" ( verbose )
|
||||||
|
Returns raw data related to "txid". If verbose is false, a string containing
|
||||||
|
hex-encoded serialized data for txid. If verbose is true a JSON object with the
|
||||||
|
following information about txid is returned:
|
||||||
|
{
|
||||||
|
"hex":"data", # String of serialized, hex encoded data for txid.
|
||||||
|
"txid":"id", # String containing the transaction id (same as "txid" parameter)
|
||||||
|
"version":n # Numeric tx version number.
|
||||||
|
"locktime":t, # Transaction locktime.
|
||||||
|
"vin":[ # Array of objects representing transaction inputs.
|
||||||
|
{
|
||||||
|
"txid":"id", # Spent transaction id as a string.
|
||||||
|
"vout""n, # Spent transaction output no.
|
||||||
|
"scriptSig":{ # Signature script as an object.
|
||||||
|
"asm":"asm", # Disassembled script string.
|
||||||
|
"hex":"hex", # Hex serialized string.
|
||||||
|
},
|
||||||
|
"sequence":n, # Script sequence number.
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
vout:[ # Array of objects representing transaction outputs.
|
||||||
|
{
|
||||||
|
"value":n, # Numeric value of output in btc.
|
||||||
|
"n", n, # Numeric output index.
|
||||||
|
"scriptPubKey":{ # Object representing pubkey script.
|
||||||
|
"asm":"asm" # Disassembled string of script.
|
||||||
|
"hex":"hex" # Hex serialized string.
|
||||||
|
"reqSigs":n, # Number of required signatures.
|
||||||
|
"type":"pubkey", # Type of scirpt. e.g. pubkeyhash" or "pubkey".
|
||||||
|
"addresses":[ # Array of address strings.
|
||||||
|
"address", # Bitcoin address.
|
||||||
|
...
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blockhash":"hash" # Hash of the block the transaction is part of.
|
||||||
|
"confirmations":n, # Number of numeric confirmations of block.
|
||||||
|
"time":t, # Transaction time in seconds since the epoch.
|
||||||
|
"blocktime":t, # Block time in seconds since the epoch.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"getreceivedbyaccount": `getreceivedbyaccount "account" ( minconf=1 )
|
||||||
|
Returns the total amount of BTC received by addresses related to "account".
|
||||||
|
Only transactions with at least "minconf" confirmations are considered.`,
|
||||||
|
|
||||||
|
"getreceivedbyaddress": `getreceivedbyaddress "address" ( minconf=1 )
|
||||||
|
Returns the total amount of BTC received by the given address. Only transactions
|
||||||
|
with "minconf" confirmations will be used for the total.`,
|
||||||
|
|
||||||
|
"gettransaction": `gettransaction "txid"
|
||||||
|
Returns a JSON object containing detailed information about the in-wallet
|
||||||
|
transaction "txid". The object follows the following format:
|
||||||
|
{
|
||||||
|
"amount":n, # Transaction amount in BTC.
|
||||||
|
"confirmations":n, # Number of confirmations for transaction.
|
||||||
|
"blockhash":"hash", # Hash of block transaction is part of.
|
||||||
|
"blockindex":n, # Index of block transaction is part of.
|
||||||
|
"blocktime":t, # Time of block transaction is part of.
|
||||||
|
"txid":"id", # Transaction id.
|
||||||
|
"time":t, # Transaction time in seconds since epoch.
|
||||||
|
"timereceived":t, # Time transaction was received in seconds since epoch.
|
||||||
|
"details":[
|
||||||
|
{
|
||||||
|
"account":"name", # The acount name involvedi n the transaction. "" means the default.
|
||||||
|
"address":"address", # The address involved in the transaction as a string.
|
||||||
|
"category":"send|receive", # Category - either send or receive.
|
||||||
|
"amount":n, # numeric amount in BTC.
|
||||||
|
}
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"gettxout": `gettxout "txid" n ( includemempool )
|
||||||
|
Returns an object containing detauls about an unspent transaction output
|
||||||
|
the "n"th output of "txid":
|
||||||
|
{
|
||||||
|
"bestblock":"hash", # Block has containing transaction.
|
||||||
|
"confirmations":n, # Number of confirmations for block.
|
||||||
|
"value":n # Transaction value in BTC.
|
||||||
|
scriptPubkey" {
|
||||||
|
"asm":"asm", # Disassembled string of script.
|
||||||
|
"hex":"hex", # String script serialized and hex encoded.
|
||||||
|
"reqSigs" # Numeric required signatures.
|
||||||
|
"type":"pubkeyhash" # Type of transaction. e.g. pubkeyhas
|
||||||
|
"addresses":[ # Array of strings containing addresses.
|
||||||
|
"address",
|
||||||
|
...
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"gettxoutsetinfo": `gettxoutsetinfo
|
||||||
|
Returns an object containing startstics about the unspent transaction
|
||||||
|
output set:
|
||||||
|
{
|
||||||
|
"height":n, # Numeric current block height.
|
||||||
|
"bestblock":"hex" # Hex string of best block hash.
|
||||||
|
"transactions":n, # Numeric count of transactions.
|
||||||
|
"txouts":n # Numeric count of transaction outputs.
|
||||||
|
"bytes_serialized":n # Numeric serialized size.
|
||||||
|
"hash_serialized" # String of serialized hash.
|
||||||
|
"total_amount":n, # Numeric total amount in BTC.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"getwork": `getwork ( "data" )
|
||||||
|
If "data" is present it is a hex encoded block datastruture that has been byte
|
||||||
|
reversed, if this is the case then the server will try to solve the
|
||||||
|
block and return true upon success, false upon failure. If data is not
|
||||||
|
specified the following object is returned to describe the work to be
|
||||||
|
solved:
|
||||||
|
{
|
||||||
|
"midstate":"xxx", # String of precomputed hash state for the first half of the data (deprecated).
|
||||||
|
"data":"...", # Block data as string.
|
||||||
|
"hash1":"..." # Hash buffer for second hash as string. (deprecated).
|
||||||
|
"target":"...", # Byte reversed hash target.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"help": `help ( "command" )
|
||||||
|
With no arguemnts, lists the supported commands. If "command" is
|
||||||
|
specified then help text for that command is returned as a string.`,
|
||||||
|
|
||||||
|
"importprivkey": `importprivkey "privkey" ( "label" rescan=true )
|
||||||
|
Adds a private key (in the same format as dumpprivkey) to the wallet. If label
|
||||||
|
is provided this is used as a label for the key. If rescan is true then the
|
||||||
|
blockchain will be scanned for transaction.`,
|
||||||
|
|
||||||
|
"importwallet": `importwallet "filename"
|
||||||
|
Imports keys from the wallet dump file in "filename".`,
|
||||||
|
|
||||||
|
"invalidateblock": `invalidateblock "hash"
|
||||||
|
Mark block specified by "hash" as invalid.`,
|
||||||
|
|
||||||
|
"keypoolrefill": `keypoolrefill ( newsize=100 )
|
||||||
|
Refills the wallet pregenerated key pool to a size of "newsize"`,
|
||||||
|
|
||||||
|
"listaccounts": `listaccounts ( minconf=1)
|
||||||
|
Returns a JSON object mapping account names to account balances:
|
||||||
|
{
|
||||||
|
"accountname": n # Account name to numeric balance in BTC.
|
||||||
|
...
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"listaddressgroupings": `listaddressgroupings
|
||||||
|
Returns a JSON array of array of addreses which have had their relation to each
|
||||||
|
other made public by common use as inputs or in the change address. The data
|
||||||
|
takes the following format:
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"address", # Bitcoin address.
|
||||||
|
amount, # Amount in BTC.
|
||||||
|
"account" # Optional account name string.
|
||||||
|
],
|
||||||
|
...
|
||||||
|
],
|
||||||
|
...
|
||||||
|
]`,
|
||||||
|
|
||||||
|
"listlockunspent": `listlockunspent
|
||||||
|
Returns a JSON array of objects detailing transaction outputs that are
|
||||||
|
temporarily unspendable due to being processed by the lockunspent call.
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"txid":"txid" # Id of locked transaction as a string.
|
||||||
|
"vout":n, # Numeric index of locked output.
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]`,
|
||||||
|
|
||||||
|
"listreceivedbyaccount": `listreceivedbyaccount ( minconf=1 includeempty=false )
|
||||||
|
Returns a JSON array containing objects for each account detailing their
|
||||||
|
balances. Only transaction with at least "minconf" confirmations will be
|
||||||
|
included. If "includeempty" is true then accounts who have received no payments
|
||||||
|
will also be included, else they will be elided. The format is as follows:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"account":"name", # Name of the receiving account.
|
||||||
|
"amount":n, # Total received amount in BTC.
|
||||||
|
"confirmations":n, # Total confirmations for most recent transaction.
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
|
||||||
|
"listreceivedbyaddress": `listreceivedbyaddress ( minconf=1 includeempty=false )
|
||||||
|
Returns a JSON array containing objects for each address detailing their
|
||||||
|
balances. Only transaction with at least "minconf" confirmations will be
|
||||||
|
included. If "includeempty" is true then adresses who have received no payments
|
||||||
|
will also be included, else they will be elided. The format is as follows:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"account":"name", # Name of the receiving account.
|
||||||
|
"amount":n, # Total received amount in BTC.
|
||||||
|
"confirmations":n, # Total confirmations for most recent transaction.
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
|
||||||
|
"listsinceblock": `listsinceblock ("blockhash" minconf=1)
|
||||||
|
Gets all wallet transactions in block since "blockhash", if blockhash is
|
||||||
|
omitted then all transactions are provided. If present the only transactions
|
||||||
|
with "minconf" confirmations are listed.
|
||||||
|
{
|
||||||
|
"transactions":[
|
||||||
|
"account" # String of account related to the transaction.
|
||||||
|
"address" # String of related address of transaction.
|
||||||
|
"category":"send|receive" # String detailing whether transaction was a send or receive of funds.
|
||||||
|
"amount":n, # Numeric value of transaction. Negative if transaction category was "send"
|
||||||
|
"fee":n, # Numeric value of transaction fee in BTC.
|
||||||
|
"confirmations":n, # Number of transaction confirmations
|
||||||
|
"blockhash":"hash" # String of hash of block transaction is part of.
|
||||||
|
"blockindex":n, # Numeric index of block transaction is part of.
|
||||||
|
"blocktime":t, # Block time in seconds since the epoch.
|
||||||
|
"txid":"id", # Transaction id.
|
||||||
|
"time":t, # Transaction time in second since the epoch.
|
||||||
|
"timereceived":t, # Time transaction received in seconds since the epoch.
|
||||||
|
"comment":"...", # String of the comment associated with the transaction.
|
||||||
|
"to":"...", # String of "to" comment of the transaction.
|
||||||
|
]
|
||||||
|
"lastblock":"lastblockhash" # Hash of the last block as a string.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"listtransactions": `listtransactions ( "account" count=10 from=0 )
|
||||||
|
Returns up to "count" most recent wallet transactions for "account" (or all
|
||||||
|
accounts if none specified) skipping the first "from" transactions.
|
||||||
|
{
|
||||||
|
"transactions":[
|
||||||
|
"account" # String of account related to the transaction.
|
||||||
|
"address" # String of related address of transaction.
|
||||||
|
"category":"send|receive|move" # String detailing whether transaction was a send or receive of funds.Move is a local move between accounts and doesnt touch the blockchain.
|
||||||
|
"amount":n, # Numeric value of transaction. Negative if transaction category was "send"
|
||||||
|
"fee":n, # Numeric value of transaction fee in BTC.
|
||||||
|
"confirmations":n, # Number of transaction confirmations
|
||||||
|
"blockhash":"hash" # String of hash of block transaction is part of.
|
||||||
|
"blockindex":n, # Numeric index of block transaction is part of.
|
||||||
|
"blocktime":t, # Block time in seconds since the epoch.
|
||||||
|
"txid":"id", # Transaction id.
|
||||||
|
"time":t, # Transaction time in second since the epoch.
|
||||||
|
"timereceived":t, # Time transaction received in seconds since the epoch.
|
||||||
|
"comment":"...", # String of the comment associated with the transaction.
|
||||||
|
"to":"...", # String of "to" comment of the transaction.
|
||||||
|
]
|
||||||
|
"lastblock":"lastblockhash" # Hash of the last block as a string.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"listunspent": `listunspent (minconf=1 maxconf=9999999 ["address",...]
|
||||||
|
Returns a JSON array of objects representing unspent transaction outputs with
|
||||||
|
between minconf and maxconf confirmations (inclusive). If the array of addresses
|
||||||
|
is present then only txouts paid to the addresses listed will be considered. The
|
||||||
|
objects take the following format:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"txid":"id", # The transaction id as a string.
|
||||||
|
"vout":n, # The output number of the tx.
|
||||||
|
"address":"add", # String of the transaction address.
|
||||||
|
"account":"acc", # The associated account, "" for default.
|
||||||
|
"scriptPubkey":"key" # The pubkeyscript as a string.
|
||||||
|
"amount":n, # The value of the transaction in BTC.
|
||||||
|
"confirmations":n, # The numer of confirmations.
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]`,
|
||||||
|
|
||||||
|
"lockunspent": `lockunspent unlock [{"txid":id", "vout":n},...]
|
||||||
|
Changes the llist of temporarily unspendable transaction outputs. The
|
||||||
|
transacion outputs in the list of objects provided will be marked as
|
||||||
|
locked (if unlock is false) or unlocked (unlock is true). A locked
|
||||||
|
transaction will not be chosen automatically to be spent when a
|
||||||
|
tranaction is created. Boolean is returned whether the command succeeded.`,
|
||||||
|
|
||||||
|
"move": `move "fromaccount" "toaccount" amount ( minconf=1 "comment" )
|
||||||
|
Moves a specifies amount from "fromaccount" to "toaccount". Only funds
|
||||||
|
with minconf confirmations are used. If comment is present this comment
|
||||||
|
will be stored in the wallet with the transaction. Boolean is returned
|
||||||
|
to denode success.`,
|
||||||
|
|
||||||
|
"ping": `ping
|
||||||
|
Queues a ping to be sent to each connected peer. Ping times are provided in
|
||||||
|
getpeerinfo.`,
|
||||||
|
|
||||||
|
"reconsiderblock": `reconsiderblock "hash"
|
||||||
|
Remove invalid mark from block specified by "hash" so it is considered again.`,
|
||||||
|
|
||||||
|
"searchrawtransactions": `searchrawtransactions "address" (verbose=1 skip=0 count=100)
|
||||||
|
Returns raw tx data related to credits or debits to "address". Skip indicates
|
||||||
|
the number of leading transactions to leave out of the final result. Count
|
||||||
|
represents the max number of transactions to return. If verbose is false, a
|
||||||
|
string containing hex-encoded serialized data for txid. If verbose is true a
|
||||||
|
JSON object with the following information about txid is returned:
|
||||||
|
{
|
||||||
|
"hex":"data", # String of serialized, hex encoded data for txid.
|
||||||
|
"txid":"id", # String containing the transaction id (same as "txid" parameter)
|
||||||
|
"version":n # Numeric tx version number.
|
||||||
|
"locktime":t, # Transaction locktime.
|
||||||
|
"vin":[ # Array of objects representing transaction inputs.
|
||||||
|
{
|
||||||
|
"txid":"id", # Spent transaction id as a string.
|
||||||
|
"vout""n, # Spent transaction output no.
|
||||||
|
"scriptSig":{ # Signature script as an object.
|
||||||
|
"asm":"asm", # Disassembled script string.
|
||||||
|
"hex":"hex", # Hex serialized string.
|
||||||
|
},
|
||||||
|
"sequence":n, # Script sequence number.
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
vout:[ # Array of objects representing transaction outputs.
|
||||||
|
{
|
||||||
|
"value":n, # Numeric value of output in btc.
|
||||||
|
"n", n, # Numeric output index.
|
||||||
|
"scriptPubKey":{ # Object representing pubkey script.
|
||||||
|
"asm":"asm" # Disassembled string of script.
|
||||||
|
"hex":"hex" # Hex serialized string.
|
||||||
|
"reqSigs":n, # Number of required signatures.
|
||||||
|
"type":"pubkey", # Type of scirpt. e.g. pubkeyhash" or "pubkey".
|
||||||
|
"addresses":[ # Array of address strings.
|
||||||
|
"address", # Bitcoin address.
|
||||||
|
...
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blockhash":"hash" # Hash of the block the transaction is part of.
|
||||||
|
"confirmations":n, # Number of numeric confirmations of block.
|
||||||
|
"time":t, # Transaction time in seconds since the epoch.
|
||||||
|
"blocktime":t, # Block time in seconds since the epoch.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"sendfrom": `sendfrom "fromaccount" "tobitcoinaddress" amount ( minconf=1 "comment" "comment-to" )
|
||||||
|
Sends "amount" (rounded to the nearest 0.00000001) to
|
||||||
|
"tobitcoindaddress" from "fromaccount". Only funds with at least
|
||||||
|
"minconf" confirmations will be considered. If "comment" is present this
|
||||||
|
will be store the purpose of the transaction. If "comment-to" is present
|
||||||
|
this comment will be recorded locally to store the recipient of the
|
||||||
|
transaction.`,
|
||||||
|
|
||||||
|
"sendmany": `sendmany "fromaccount" {"address":amount,...} ( minconf=1 "comment" )
|
||||||
|
Sends funds to multiple recipients. Funds from "fromaccount" are send to the
|
||||||
|
address/amount pairs specified. Only funds with minconf confirmations are
|
||||||
|
considered. "comment" if present is recorded locally as the purpose of the
|
||||||
|
transaction.`,
|
||||||
|
|
||||||
|
"sendrawtransaction": `sendrawtransaction "hexstring" (allowhighfees=false)
|
||||||
|
Submits the hex-encoded transaction in "hexstring" to the bitcoin network via
|
||||||
|
the local node. If allowhighfees is true then high fees will be allowed.`,
|
||||||
|
|
||||||
|
"sendtoaddress": `sendtoaddress "bitcoindaddress" amount ( "comment" "comment-to")
|
||||||
|
Send "amount" BTC (rounded to the nearest 0.00000001) to "bitcoinaddress". If
|
||||||
|
comment is set, it will be stored locally to describe the purpose of the
|
||||||
|
transaction. If comment-to" is set it will be stored locally to describe the
|
||||||
|
recipient of the transaction. A string containing the transaction id is
|
||||||
|
returned if successful.`,
|
||||||
|
|
||||||
|
"setaccount": `setaccount "address" "account"
|
||||||
|
Sets the account associated with "address" to "account".`,
|
||||||
|
|
||||||
|
"setgenerate": `setgenerate generate ( genproclimit )
|
||||||
|
Sets the current mining state to "generate". Up to "genproclimit" processors
|
||||||
|
will be used, if genproclimit is -1 then it is unlimited.`,
|
||||||
|
|
||||||
|
"settxfee": `settxfee amount
|
||||||
|
Sets the current transaction fee.`,
|
||||||
|
|
||||||
|
"signmessage": `signmessage "address" "message"
|
||||||
|
Returns a signature for "message" with the private key of "address"`,
|
||||||
|
|
||||||
|
"signrawtransaction": `signrawtransaction "hexstring" ( prevtxs ["privatekey",...] sighashtype="ALL" )
|
||||||
|
Signs the inputs for the serialized and hex encoded transaction in
|
||||||
|
"hexstring". "prevtxs" optionally is an is an array of objects
|
||||||
|
representing the previous tx outputs that are spent here but may not yet
|
||||||
|
be in the blockchain, it takes the following format:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"txid":id:, # String of transaction id.
|
||||||
|
"vout":n, # Output number.
|
||||||
|
"scriptPubKey":"hex", # Hex encoded string of pubkey script from transaction.
|
||||||
|
"redemScript":"hex" # Hex encoded redemption script.
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
If the third argument is provided these base58-encoded private keys in
|
||||||
|
the list will be the only keys used to sign the transaction. sighashtype
|
||||||
|
optionally denoes the signature hash type to be used. is must be one of the
|
||||||
|
following:
|
||||||
|
"ALL",
|
||||||
|
"NONE",
|
||||||
|
"SINGLE",
|
||||||
|
"ALL|ANYONECANPAY",
|
||||||
|
"NONE|ANYONECANPAY",
|
||||||
|
"SINGLE|ANYONECANPAY".
|
||||||
|
The return value takes the following format:
|
||||||
|
{
|
||||||
|
"hex":"value" # Hex string of raw transactino with signatures applied.
|
||||||
|
"complete":n, # If the transaction has a complete set of signatures. 0 if false.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"stop": `stop
|
||||||
|
Stop the server.`,
|
||||||
|
|
||||||
|
"submitblock": `submitblock "data" ( optionalparameterobject )
|
||||||
|
Will attempt to submit the block serialized in "data" to the bitcoin network.
|
||||||
|
optionalparametersobject takes the following format:
|
||||||
|
{
|
||||||
|
"workid":"id" # If getblocktemplate provided a workid it must be included with submissions.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"validateaddress": `validateaddress "address
|
||||||
|
Returns a JSON objects containing information about the provided "address".
|
||||||
|
Format:
|
||||||
|
{
|
||||||
|
"isvalid":true|false, # If the address is valid. If false this is the only property returned.
|
||||||
|
"address:"address", # The address that was validated.
|
||||||
|
"ismine":true|false, # If the address belongs to the server.
|
||||||
|
"isscript:true|false, # If the address is a script address.
|
||||||
|
"pubkey":"pk", # The hex value of the public key associated with the address.
|
||||||
|
"iscompressed":true|false, # If the address is compresssed.
|
||||||
|
"account":"account", # The related account. "" is the default.
|
||||||
|
}`,
|
||||||
|
|
||||||
|
"verifychain": `verifychain ( checklevel=3 numblocks=288 )
|
||||||
|
Verifies the stored blockchain database. "checklevel" denotes how thorough the
|
||||||
|
check is and "numblocks" how many blocks from the tip will be verified.`,
|
||||||
|
|
||||||
|
"verifymessage": `verifymessage "bitcoinaddress" "signature" "message"
|
||||||
|
Verifies the signature "signature" for "message" from the key related
|
||||||
|
to "bitcoinaddress". The return is true or false if the signature
|
||||||
|
verified correctly.`,
|
||||||
|
|
||||||
|
"walletlock": `walletlock
|
||||||
|
Removes any encryption key for the wallet from memory, unlocking the wallet.
|
||||||
|
In order to use wallet functionality again the wallet must be unlocked via
|
||||||
|
walletpassphrase.`,
|
||||||
|
|
||||||
|
"walletpassphrase": `walletpassphrase "passphrase" timeout
|
||||||
|
Decrypts the wallet for "timeout" seconds with "passphrase". This is required
|
||||||
|
before using wallet functionality.`,
|
||||||
|
|
||||||
|
"walletpassphrasechange": `walletpassphrasechange "oldpassphrase" "newpassphrase"
|
||||||
|
Changes the wallet passphrase from "oldpassphrase" to "newpassphrase".`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHelpString returns a string containing help text for "cmdName". If
|
||||||
|
// cmdName is unknown to btcjson - either via the default command list or those
|
||||||
|
// registered using RegisterCustomCmd - an error will be returned.
|
||||||
|
func GetHelpString(cmdName string) (string, error) {
|
||||||
|
helpstr := ""
|
||||||
|
|
||||||
|
if help, ok := defaultHelpStrings[cmdName]; ok {
|
||||||
|
return help, nil
|
||||||
|
}
|
||||||
|
if c, ok := customCmds[cmdName]; ok {
|
||||||
|
return c.helpString, nil
|
||||||
|
}
|
||||||
|
return helpstr, errors.New("invalid command specified")
|
||||||
|
}
|
116
btcjson/doc.go
Normal file
116
btcjson/doc.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package btcjson 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
|
||||||
|
messages. It does not provide any code for the client to actually deal
|
||||||
|
with the messages. Although it is meant primarily for btcd, it is
|
||||||
|
possible to use this code elsewhere for interacting with a bitcoin
|
||||||
|
client programatically.
|
||||||
|
|
||||||
|
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":"SOMEID"}
|
||||||
|
|
||||||
|
The result field can be as simple as an int, or a complex structure
|
||||||
|
containing many nested fields. For cases where we have already worked
|
||||||
|
out the possible types of reply, result is unmarshalled into a
|
||||||
|
structure that matches the command. For others, an interface is
|
||||||
|
returned. An interface is not as convenient as one needs to do a type
|
||||||
|
assertion first before using the value, but it means we can handle
|
||||||
|
arbitrary replies.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
RPC Server Authentication
|
||||||
|
|
||||||
|
All RPC calls must be authenticated with a username and password. The specifics
|
||||||
|
on how to configure the RPC server varies depending on the specific bitcoin
|
||||||
|
implementation. For bitcoind, this is accomplished by setting rpcuser and
|
||||||
|
rpcpassword in the file ~/.bitcoin/bitcoin.conf. For btcd, this is accomplished
|
||||||
|
by setting rpcuser and rpcpass in the file ~/.btcd/btcd.conf. The default local
|
||||||
|
address of bitcoind is 127.0.0.1:8332 and the default local address of btcd is
|
||||||
|
127.0.0.1:8334
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
The general pattern to use this package consists of generating a message (see
|
||||||
|
the full list on the official bitcoin wiki), sending the message, and handling
|
||||||
|
the result after asserting its type.
|
||||||
|
|
||||||
|
For commands where the reply structure is known, such as getinfo, one can
|
||||||
|
directly access the fields in the Reply structure by type asserting the
|
||||||
|
reply to the appropriate concrete type.
|
||||||
|
|
||||||
|
// Create a getinfo command.
|
||||||
|
id := 1
|
||||||
|
cmd, err := btcjson.NewGetInfoCmd(id)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle error.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the message to server using the appropriate username and
|
||||||
|
// password.
|
||||||
|
reply, err := btcjson.RpcSend(user, password, server, cmd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
// Log and handle error.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure there is a result and type assert it to a btcjson.InfoResult.
|
||||||
|
if reply.Result != nil {
|
||||||
|
if info, ok := reply.Result.(*btcjson.InfoResult); ok {
|
||||||
|
fmt.Println("balance =", info.Balance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
For other commands where this package does not yet provide a concrete
|
||||||
|
implementation for the reply, such as getrawmempool, the reply uses a generic
|
||||||
|
interface so one can access individual items as follows:
|
||||||
|
|
||||||
|
// Create a getrawmempool command.
|
||||||
|
id := 1
|
||||||
|
cmd, err := btcjson.NewGetRawMempoolCmd(id)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle error.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the message to server using the appropriate username and
|
||||||
|
// password.
|
||||||
|
reply, err := btcjson.RpcSend(user, password, server, cmd)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle error.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure there is a result and type assert it to a string slice.
|
||||||
|
if reply.Result != nil {
|
||||||
|
if mempool, ok := reply.Result.([]string); ok {
|
||||||
|
fmt.Println("num mempool entries =", len(mempool))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package btcjson
|
59
btcjson/internal_test.go
Normal file
59
btcjson/internal_test.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the btcjson package rather than than the
|
||||||
|
btcjson_test package so it can bridge access to the internals to properly test
|
||||||
|
cases which are either not possible or can't reliably be tested via the public
|
||||||
|
interface. The functions are only exported while the tests are being run.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TestJsonWIthArgs tests jsonWithArgs to ensure that it can generate a json
|
||||||
|
// command as well as sending it something that cannot be marshalled to json
|
||||||
|
// (a channel) to test the failure paths.
|
||||||
|
func TestJsonWithArgs(t *testing.T) {
|
||||||
|
cmd := "list"
|
||||||
|
var args interface{}
|
||||||
|
_, err := jsonWithArgs(cmd, "test", args)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Could not make json with no args. %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
channel := make(chan int)
|
||||||
|
_, err = jsonWithArgs(cmd, "test", channel)
|
||||||
|
if _, ok := err.(*json.UnsupportedTypeError); !ok {
|
||||||
|
t.Errorf("Message with channel should fail. %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var comp complex128
|
||||||
|
_, err = jsonWithArgs(cmd, "test", comp)
|
||||||
|
if _, ok := err.(*json.UnsupportedTypeError); !ok {
|
||||||
|
t.Errorf("Message with complex part should fail. %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestJsonRPCSend tests jsonRPCSend which actually send the rpc command.
|
||||||
|
// This currently a negative test only until we setup a fake http server to
|
||||||
|
// test the actually connection code.
|
||||||
|
func TestJsonRPCSend(t *testing.T) {
|
||||||
|
// Just negative test right now.
|
||||||
|
user := "something"
|
||||||
|
password := "something"
|
||||||
|
server := "invalid"
|
||||||
|
var message []byte
|
||||||
|
_, err := jsonRPCSend(user, password, server, message, false, nil, false)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Should fail when it cannot connect.")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
821
btcjson/jsonapi.go
Normal file
821
btcjson/jsonapi.go
Normal file
|
@ -0,0 +1,821 @@
|
||||||
|
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BadStatusCode describes a HTTP error when a response has non-200 status code
|
||||||
|
type BadStatusCode int
|
||||||
|
|
||||||
|
func (e BadStatusCode) Error() string {
|
||||||
|
status := int(e)
|
||||||
|
return fmt.Sprintf("http bad status: %d %s", status, http.StatusText(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrIncorrectArgTypes describes an error where the wrong argument types
|
||||||
|
// are present.
|
||||||
|
var ErrIncorrectArgTypes = errors.New("incorrect arguement types")
|
||||||
|
|
||||||
|
// Message contains a message to be sent to the bitcoin client.
|
||||||
|
type Message struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
Id interface{} `json:"id,omitempty"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params interface{} `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply is the general form of the reply from the bitcoin client.
|
||||||
|
// The form of the Result part varies from one command to the next so it
|
||||||
|
// is currently implemented as an interface.
|
||||||
|
type Reply struct {
|
||||||
|
Result interface{} `json:"result"`
|
||||||
|
Error *Error `json:"error"`
|
||||||
|
// This has to be a pointer for go to put a null in it when empty.
|
||||||
|
Id *interface{} `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error models the error field of the json returned by a bitcoin client. When
|
||||||
|
// there is no error, this should be a nil pointer to produce the null in the
|
||||||
|
// json that bitcoind produces.
|
||||||
|
type Error struct {
|
||||||
|
Code int `json:"code,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guarantee Error satisifies the builtin error interface
|
||||||
|
var _, _ error = Error{}, &Error{}
|
||||||
|
|
||||||
|
// Error returns a string describing the btcjson error. This
|
||||||
|
// satisifies the builtin error interface.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return fmt.Sprintf("%d: %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonWithArgs takes a command, an id, and an interface which contains an
|
||||||
|
// array of the arguments for that command. It knows NOTHING about the commands
|
||||||
|
// so all error checking of the arguments must happen before it is called.
|
||||||
|
func jsonWithArgs(command string, id interface{}, args interface{}) ([]byte, error) {
|
||||||
|
rawMessage := Message{"1.0", id, command, args}
|
||||||
|
finalMessage, err := json.Marshal(rawMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return finalMessage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMessage takes a string and the optional arguments for it. Then,
|
||||||
|
// if it is a recognized bitcoin json message, generates the json message ready
|
||||||
|
// to send off to the daemon or server.
|
||||||
|
// It is capable of handeling all of the commands from the standard client,
|
||||||
|
// described at:
|
||||||
|
// https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list
|
||||||
|
//
|
||||||
|
// WARNING: This method is deprecated and may be removed in a future version.
|
||||||
|
// Do NOT use this as it does not work for all commands. Instead, use one of
|
||||||
|
// the New<command>Cmd functions to create a specific command.
|
||||||
|
func CreateMessage(message string, args ...interface{}) ([]byte, error) {
|
||||||
|
finalMessage, err := CreateMessageWithId(message, "btcd", args...)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMessageWithId takes a string, an id, and the optional arguments for
|
||||||
|
// it. Then, if it is a recognized bitcoin json message, generates the json
|
||||||
|
// message ready to send off to the daemon or server. It is capable of handling
|
||||||
|
// all of the commands from the standard client, described at:
|
||||||
|
// https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list
|
||||||
|
//
|
||||||
|
// WARNING: This method is deprecated and may be removed in a future version.
|
||||||
|
// Do NOT use this as it does not work for all commands. Instead, use one of
|
||||||
|
// the New<command>Cmd functions to create a specific command.
|
||||||
|
func CreateMessageWithId(message string, id interface{}, args ...interface{}) ([]byte, error) {
|
||||||
|
var finalMessage []byte
|
||||||
|
var err error
|
||||||
|
// Different commands have different required and optional arguments.
|
||||||
|
// Need to handle them based on that.
|
||||||
|
switch message {
|
||||||
|
// No args
|
||||||
|
case "getblockcount", "getblocknumber", "getconnectioncount",
|
||||||
|
"getdifficulty", "getgenerate", "gethashespersec", "getinfo",
|
||||||
|
"getmininginfo", "getpeerinfo", "getrawmempool",
|
||||||
|
"keypoolrefill", "listaddressgroupings", "listlockunspent",
|
||||||
|
"stop", "walletlock", "getbestblockhash", "getblockchaininfo",
|
||||||
|
"getnetworkinfo":
|
||||||
|
if len(args) > 0 {
|
||||||
|
err = fmt.Errorf("too many arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One optional int
|
||||||
|
case "listaccounts":
|
||||||
|
if len(args) > 1 {
|
||||||
|
err = fmt.Errorf("too many arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
if len(args) == 1 {
|
||||||
|
_, ok := args[0].(int)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("argument must be int for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required int
|
||||||
|
case "getblockhash", "estimatefee", "estimatepriority":
|
||||||
|
if len(args) != 1 {
|
||||||
|
err = fmt.Errorf("missing argument for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok := args[0].(int)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("argument must be int for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required float
|
||||||
|
case "settxfee":
|
||||||
|
if len(args) != 1 {
|
||||||
|
err = fmt.Errorf("missing argument for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok := args[0].(float64)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("argument must be float64 for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One optional string
|
||||||
|
case "getmemorypool", "getnewaddress", "getwork", "help",
|
||||||
|
"getrawchangeaddress":
|
||||||
|
if len(args) > 1 {
|
||||||
|
err = fmt.Errorf("too many arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
if len(args) == 1 {
|
||||||
|
_, ok := args[0].(string)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("optional argument must be string for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required string
|
||||||
|
case "backupwallet", "decoderawtransaction", "dumpprivkey",
|
||||||
|
"encryptwallet", "getaccount", "getaccountaddress",
|
||||||
|
"getaddressesbyaccount", "getblock",
|
||||||
|
"gettransaction", "sendrawtransaction", "validateaddress",
|
||||||
|
"invalidateblock", "reconsiderblock":
|
||||||
|
if len(args) != 1 {
|
||||||
|
err = fmt.Errorf("%s requires one argument", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok := args[0].(string)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("argument must be string for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// Two required strings
|
||||||
|
case "setaccount", "signmessage", "walletpassphrasechange", "addnode":
|
||||||
|
if len(args) != 2 {
|
||||||
|
err = fmt.Errorf("missing arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
_, ok2 := args[1].(string)
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("arguments must be string for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required string, one required int
|
||||||
|
case "walletpassphrase":
|
||||||
|
if len(args) != 2 {
|
||||||
|
err = fmt.Errorf("missing arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
_, ok2 := args[1].(int)
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("arguments must be string and int for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// Three required strings
|
||||||
|
case "verifymessage":
|
||||||
|
if len(args) != 3 {
|
||||||
|
err = fmt.Errorf("three arguments required for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
_, ok2 := args[1].(string)
|
||||||
|
_, ok3 := args[2].(string)
|
||||||
|
if !ok1 || !ok2 || !ok3 {
|
||||||
|
err = fmt.Errorf("arguments must be string for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required bool, one optional string
|
||||||
|
case "getaddednodeinfo":
|
||||||
|
if len(args) > 2 || len(args) == 0 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(bool)
|
||||||
|
ok2 := true
|
||||||
|
if len(args) == 2 {
|
||||||
|
_, ok2 = args[1].(string)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("arguments must be bool and optionally string for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required bool, one optional int
|
||||||
|
case "setgenerate":
|
||||||
|
if len(args) > 2 || len(args) == 0 {
|
||||||
|
err = fmt.Errorf("wrong number of argument for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(bool)
|
||||||
|
ok2 := true
|
||||||
|
if len(args) == 2 {
|
||||||
|
_, ok2 = args[1].(int)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("arguments must be bool and optionally int for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One optional string, one optional int
|
||||||
|
case "getbalance", "getreceivedbyaccount":
|
||||||
|
if len(args) > 2 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
ok1 := true
|
||||||
|
ok2 := true
|
||||||
|
if len(args) >= 1 {
|
||||||
|
_, ok1 = args[0].(string)
|
||||||
|
}
|
||||||
|
if len(args) == 2 {
|
||||||
|
_, ok2 = args[1].(int)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("optional arguments must be string and int for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required string, one optional int
|
||||||
|
case "getrawtransaction", "getreceivedbyaddress":
|
||||||
|
if len(args) > 2 || len(args) == 0 {
|
||||||
|
err = fmt.Errorf("wrong number of argument for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
ok2 := true
|
||||||
|
if len(args) == 2 {
|
||||||
|
_, ok2 = args[1].(int)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("arguments must be string and optionally int for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required string, one optional string
|
||||||
|
// Strictly, the optional arg for submit block is an object, but
|
||||||
|
// bitcoind ignores it for now, so best to just allow string until
|
||||||
|
// support for it is complete.
|
||||||
|
case "submitblock":
|
||||||
|
if len(args) > 2 || len(args) == 0 {
|
||||||
|
err = fmt.Errorf("wrong number of argument for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
ok2 := true
|
||||||
|
if len(args) == 2 {
|
||||||
|
_, ok2 = args[1].(string)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("arguments must be string and optionally string for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One optional int, one optional bool
|
||||||
|
case "listreceivedbyaccount", "listreceivedbyaddress":
|
||||||
|
if len(args) > 2 {
|
||||||
|
err = fmt.Errorf("wrong number of argument for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
ok1 := true
|
||||||
|
ok2 := true
|
||||||
|
if len(args) >= 1 {
|
||||||
|
_, ok1 = args[0].(int)
|
||||||
|
}
|
||||||
|
if len(args) == 2 {
|
||||||
|
_, ok2 = args[1].(bool)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("optional arguments must be int and bool for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One optional string, two optional ints
|
||||||
|
case "listtransactions":
|
||||||
|
if len(args) > 3 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
ok1 := true
|
||||||
|
ok2 := true
|
||||||
|
ok3 := true
|
||||||
|
if len(args) >= 1 {
|
||||||
|
_, ok1 = args[0].(string)
|
||||||
|
}
|
||||||
|
if len(args) > 1 {
|
||||||
|
_, ok2 = args[1].(int)
|
||||||
|
}
|
||||||
|
if len(args) == 3 {
|
||||||
|
_, ok3 = args[2].(int)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 || !ok3 {
|
||||||
|
err = fmt.Errorf("optional arguments must be string and up to two ints for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required string, one optional string, one optional bool
|
||||||
|
case "importprivkey":
|
||||||
|
if len(args) > 3 || len(args) == 0 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
ok2 := true
|
||||||
|
ok3 := true
|
||||||
|
if len(args) > 1 {
|
||||||
|
_, ok2 = args[1].(string)
|
||||||
|
}
|
||||||
|
if len(args) == 3 {
|
||||||
|
_, ok3 = args[2].(bool)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 || !ok3 {
|
||||||
|
err = fmt.Errorf("arguments must be string and optionally string and bool for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// Two optional ints
|
||||||
|
case "listunspent":
|
||||||
|
if len(args) > 2 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
ok1 := true
|
||||||
|
ok2 := true
|
||||||
|
if len(args) >= 1 {
|
||||||
|
_, ok1 = args[0].(int)
|
||||||
|
}
|
||||||
|
if len(args) == 2 {
|
||||||
|
_, ok2 = args[1].(int)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("optional arguments must be ints for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// Two optional strings
|
||||||
|
case "listsinceblock":
|
||||||
|
if len(args) > 2 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
ok1 := true
|
||||||
|
ok2 := true
|
||||||
|
if len(args) >= 1 {
|
||||||
|
_, ok1 = args[0].(string)
|
||||||
|
}
|
||||||
|
if len(args) == 2 {
|
||||||
|
_, ok2 = args[1].(string)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
err = fmt.Errorf("optional arguments must be strings for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
|
||||||
|
// Two required strings, one required float, one optional int,
|
||||||
|
// two optional strings.
|
||||||
|
case "sendfrom":
|
||||||
|
if len(args) > 6 || len(args) < 3 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
_, ok2 := args[1].(string)
|
||||||
|
_, ok3 := args[2].(float64)
|
||||||
|
ok4 := true
|
||||||
|
ok5 := true
|
||||||
|
ok6 := true
|
||||||
|
if len(args) >= 4 {
|
||||||
|
_, ok4 = args[3].(int)
|
||||||
|
}
|
||||||
|
if len(args) >= 5 {
|
||||||
|
_, ok5 = args[4].(string)
|
||||||
|
}
|
||||||
|
if len(args) == 6 {
|
||||||
|
_, ok6 = args[5].(string)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 || !ok6 {
|
||||||
|
err = fmt.Errorf("arguments must be string, string, float64 and optionally int and two strings for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// Two required strings, one required float, one optional int,
|
||||||
|
// one optional string.
|
||||||
|
case "move":
|
||||||
|
if len(args) > 5 || len(args) < 3 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
_, ok2 := args[1].(string)
|
||||||
|
_, ok3 := args[2].(float64)
|
||||||
|
ok4 := true
|
||||||
|
ok5 := true
|
||||||
|
if len(args) >= 4 {
|
||||||
|
_, ok4 = args[3].(int)
|
||||||
|
}
|
||||||
|
if len(args) == 5 {
|
||||||
|
_, ok5 = args[4].(string)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 {
|
||||||
|
err = fmt.Errorf("arguments must be string, string, float64 and optionally int and string for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// One required strings, one required float, two optional strings
|
||||||
|
case "sendtoaddress":
|
||||||
|
if len(args) > 4 || len(args) < 2 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
_, ok2 := args[1].(float64)
|
||||||
|
ok3 := true
|
||||||
|
ok4 := true
|
||||||
|
if len(args) >= 3 {
|
||||||
|
_, ok3 = args[2].(string)
|
||||||
|
}
|
||||||
|
if len(args) == 4 {
|
||||||
|
_, ok4 = args[3].(string)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 || !ok3 || !ok4 {
|
||||||
|
err = fmt.Errorf("arguments must be string, float64 and optionally two strings for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// required int, required pair of keys (string), optional string
|
||||||
|
case "addmultisignaddress":
|
||||||
|
if len(args) > 4 || len(args) < 3 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(int)
|
||||||
|
_, ok2 := args[1].(string)
|
||||||
|
_, ok3 := args[2].(string)
|
||||||
|
ok4 := true
|
||||||
|
if len(args) == 4 {
|
||||||
|
_, ok4 = args[2].(string)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 || !ok3 || !ok4 {
|
||||||
|
err = fmt.Errorf("arguments must be int, two string and optionally one for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// Must be a set of string, int, string, float (any number of those).
|
||||||
|
case "createrawtransaction":
|
||||||
|
if len(args)%4 != 0 || len(args) == 0 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
type vlist struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
}
|
||||||
|
vList := make([]vlist, len(args)/4)
|
||||||
|
addresses := make(map[string]float64)
|
||||||
|
for i := 0; i < len(args)/4; i++ {
|
||||||
|
txid, ok1 := args[(i*4)+0].(string)
|
||||||
|
vout, ok2 := args[(i*4)+1].(uint32)
|
||||||
|
add, ok3 := args[(i*4)+2].(string)
|
||||||
|
amt, ok4 := args[(i*4)+3].(float64)
|
||||||
|
if !ok1 || !ok2 || !ok3 || !ok4 {
|
||||||
|
return finalMessage, ErrIncorrectArgTypes
|
||||||
|
}
|
||||||
|
vList[i].Txid = txid
|
||||||
|
vList[i].Vout = vout
|
||||||
|
addresses[add] = amt
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, []interface{}{vList, addresses})
|
||||||
|
// string, string/float pairs, optional int, and string
|
||||||
|
case "sendmany":
|
||||||
|
if len(args) < 3 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
var minconf int
|
||||||
|
var comment string
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
if !ok1 {
|
||||||
|
return finalMessage, ErrIncorrectArgTypes
|
||||||
|
}
|
||||||
|
addresses := make(map[string]float64)
|
||||||
|
for i := 1; i < len(args); i += 2 {
|
||||||
|
add, ok1 := args[i].(string)
|
||||||
|
if ok1 {
|
||||||
|
if len(args) > i+1 {
|
||||||
|
amt, ok2 := args[i+1].(float64)
|
||||||
|
if !ok2 {
|
||||||
|
return finalMessage, ErrIncorrectArgTypes
|
||||||
|
}
|
||||||
|
// Put a single pair into addresses
|
||||||
|
addresses[add] = amt
|
||||||
|
} else {
|
||||||
|
comment = add
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := args[i].(int); ok {
|
||||||
|
minconf = args[i].(int)
|
||||||
|
}
|
||||||
|
if len(args)-1 > i {
|
||||||
|
if _, ok := args[i+1].(string); ok {
|
||||||
|
comment = args[i+1].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, []interface{}{args[0].(string), addresses, minconf, comment})
|
||||||
|
// bool and an array of stuff
|
||||||
|
case "lockunspent":
|
||||||
|
if len(args) < 2 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(bool)
|
||||||
|
if !ok1 {
|
||||||
|
return finalMessage, ErrIncorrectArgTypes
|
||||||
|
}
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, args)
|
||||||
|
// one required string (hex) and optional sets of one string, one int,
|
||||||
|
// and one string along with another optional string.
|
||||||
|
case "signrawtransaction":
|
||||||
|
if len(args) < 1 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
if !ok1 {
|
||||||
|
return finalMessage, ErrIncorrectArgTypes
|
||||||
|
}
|
||||||
|
type txlist struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
ScriptPubKey string `json:"scriptPubKey"`
|
||||||
|
}
|
||||||
|
txList := make([]txlist, 1)
|
||||||
|
|
||||||
|
if len(args) > 1 {
|
||||||
|
txid, ok2 := args[1].(string)
|
||||||
|
vout, ok3 := args[2].(uint32)
|
||||||
|
spkey, ok4 := args[3].(string)
|
||||||
|
if !ok1 || !ok2 || !ok3 || !ok4 {
|
||||||
|
return finalMessage, ErrIncorrectArgTypes
|
||||||
|
}
|
||||||
|
txList[0].Txid = txid
|
||||||
|
txList[0].Vout = vout
|
||||||
|
txList[0].ScriptPubKey = spkey
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
pkeyList := make([]string, (len(args)-1)/4)
|
||||||
|
for i := 0; i < len(args)/4; i += 1 {
|
||||||
|
fmt.Println(args[(i*4)+4])
|
||||||
|
txid, ok1 := args[(i*4)+1].(string)
|
||||||
|
vout, ok2 := args[(i*4)+2].(int)
|
||||||
|
spkey, ok3 := args[(i*4)+3].(string)
|
||||||
|
pkey, ok4 := args[(i*4)+4].(string)
|
||||||
|
if !ok1 || !ok2 || !ok3 || !ok4 {
|
||||||
|
return finalMessage, ErrIncorrectArgTypes
|
||||||
|
}
|
||||||
|
txList[i].Txid = txid
|
||||||
|
txList[i].Vout = vout
|
||||||
|
txList[i].ScriptPubKey = spkey
|
||||||
|
pkeyList[i] = pkey
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
finalMessage, err = jsonWithArgs(message, id, []interface{}{args[0].(string), txList})
|
||||||
|
// one required string (address), one optional bool, two optional ints.
|
||||||
|
case "searchrawtransactions":
|
||||||
|
if len(args) > 4 || len(args) == 0 {
|
||||||
|
err = fmt.Errorf("wrong number of arguments for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
_, ok1 := args[0].(string)
|
||||||
|
ok2 := true
|
||||||
|
ok3 := true
|
||||||
|
ok4 := true
|
||||||
|
if len(args) >= 2 {
|
||||||
|
_, ok2 = args[1].(int)
|
||||||
|
}
|
||||||
|
if len(args) >= 3 {
|
||||||
|
_, ok3 = args[2].(int)
|
||||||
|
}
|
||||||
|
if len(args) == 4 {
|
||||||
|
_, ok4 = args[3].(int)
|
||||||
|
}
|
||||||
|
if !ok1 || !ok2 || !ok3 || !ok4 {
|
||||||
|
err = fmt.Errorf("arguments must be string, one optional "+
|
||||||
|
"bool, and two optional ints for %s", message)
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
// Any other message
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("not a valid command: %s", message)
|
||||||
|
}
|
||||||
|
return finalMessage, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONGetMethod takes a message and tries to find the bitcoin command that it
|
||||||
|
// is in reply to so it can be processed further.
|
||||||
|
func JSONGetMethod(message []byte) (string, error) {
|
||||||
|
var obj struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(message, &obj)
|
||||||
|
return obj.Method, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TlsRpcCommand takes a message generated from one of the routines above
|
||||||
|
// along with the login/server information and any relavent PEM encoded
|
||||||
|
// certificates chains. It sends the command via https and returns a go struct
|
||||||
|
// with the result.
|
||||||
|
func TlsRpcCommand(user string, password string, server string, message []byte,
|
||||||
|
certificates []byte, skipverify bool) (Reply, error) {
|
||||||
|
return rpcCommand(user, password, server, message, true, certificates,
|
||||||
|
skipverify)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RpcCommand takes a message generated from one of the routines above
|
||||||
|
// along with the login/server info, sends it, and gets a reply, returning
|
||||||
|
// a go struct with the result.
|
||||||
|
func RpcCommand(user string, password string, server string, message []byte) (Reply, error) {
|
||||||
|
return rpcCommand(user, password, server, message, false, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcCommand(user string, password string, server string, message []byte,
|
||||||
|
https bool, certificates []byte, skipverify bool) (Reply, error) {
|
||||||
|
var result Reply
|
||||||
|
method, err := JSONGetMethod(message)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
body, err := rpcRawCommand(user, password, server, message, https,
|
||||||
|
certificates, skipverify)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("error getting json reply: %v", err)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
result, err = ReadResultCmd(method, body)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("error reading json message: %v", err)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TlsRpcRawCommand takes a message generated from one of the routines above
|
||||||
|
// along with the login,server info and PEM encoded certificate chains for the
|
||||||
|
// server sends it, and gets a reply, returning
|
||||||
|
// the raw []byte response for use with ReadResultCmd.
|
||||||
|
func TlsRpcRawCommand(user string, password string, server string,
|
||||||
|
message []byte, certificates []byte, skipverify bool) ([]byte, error) {
|
||||||
|
return rpcRawCommand(user, password, server, message, true,
|
||||||
|
certificates, skipverify)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RpcRawCommand takes a message generated from one of the routines above
|
||||||
|
// along with the login/server info, sends it, and gets a reply, returning
|
||||||
|
// the raw []byte response for use with ReadResultCmd.
|
||||||
|
func RpcRawCommand(user string, password string, server string, message []byte) ([]byte, error) {
|
||||||
|
return rpcRawCommand(user, password, server, message, false, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rpcRawCommand is a helper function for the above two functions.
|
||||||
|
func rpcRawCommand(user string, password string, server string,
|
||||||
|
message []byte, https bool, certificates []byte, skipverify bool) ([]byte, error) {
|
||||||
|
var result []byte
|
||||||
|
var msg interface{}
|
||||||
|
err := json.Unmarshal(message, &msg)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("error, message does not appear to be valid json: %v", err)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
resp, err := jsonRPCSend(user, password, server, message, https,
|
||||||
|
certificates, skipverify)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("error sending json message: " + err.Error())
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, BadStatusCode(resp.StatusCode)
|
||||||
|
}
|
||||||
|
result, err = GetRaw(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("error getting json reply: %v", err)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RpcSend sends the passed command to the provided server using the provided
|
||||||
|
// authentication details, waits for a reply, and returns a Go struct with the
|
||||||
|
// result.
|
||||||
|
func RpcSend(user string, password string, server string, cmd Cmd) (Reply, error) {
|
||||||
|
msg, err := cmd.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return Reply{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return RpcCommand(user, password, server, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TlsRpcSend sends the passed command to the provided server using the provided
|
||||||
|
// authentication details and PEM encoded certificate chain, waits for a reply,
|
||||||
|
// and returns a Go struct with the result.
|
||||||
|
func TlsRpcSend(user string, password string, server string, cmd Cmd,
|
||||||
|
certificates []byte, skipVerify bool) (Reply, error) {
|
||||||
|
|
||||||
|
msg, err := cmd.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return Reply{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return TlsRpcCommand(user, password, server, msg, certificates,
|
||||||
|
skipVerify)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidIdType checks that the Id field (which can go in any of the json
|
||||||
|
// messages) is valid. json rpc 1.0 allows any (json) type, but we still need
|
||||||
|
// to prevent values that cannot be marshalled from going in. json rpc 2.0
|
||||||
|
// (which bitcoind follows for some parts) only allows string, number, or null,
|
||||||
|
// so we restrict to that list. Ths is only necessary if you manually marshal
|
||||||
|
// a message. The normal btcjson functions only use string ids.
|
||||||
|
func IsValidIdType(id interface{}) bool {
|
||||||
|
switch id.(type) {
|
||||||
|
case int, int8, int16, int32, int64,
|
||||||
|
uint, uint8, uint16, uint32, uint64,
|
||||||
|
float32, float64,
|
||||||
|
string,
|
||||||
|
nil:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONToAmount Safely converts a floating point value to an int.
|
||||||
|
// Clearly not all floating point numbers can be converted to ints (there
|
||||||
|
// is no one-to-one mapping), but bitcoin's json api returns most numbers as
|
||||||
|
// floats which are not safe to use when handling money. Since bitcoins can
|
||||||
|
// only be divided in a limited way, this methods works for the amounts returned
|
||||||
|
// by the json api. It is not for general use.
|
||||||
|
// This follows the method described at:
|
||||||
|
// https://en.bitcoin.it/wiki/Proper_Money_Handling_%28JSON-RPC%29
|
||||||
|
func JSONToAmount(jsonAmount float64) (int64, error) {
|
||||||
|
var amount int64
|
||||||
|
var err error
|
||||||
|
if jsonAmount > 1.797693134862315708145274237317043567981e+300 {
|
||||||
|
err := fmt.Errorf("error %v is too large to convert", jsonAmount)
|
||||||
|
return amount, err
|
||||||
|
}
|
||||||
|
if jsonAmount < -1.797693134862315708145274237317043567981e+300 {
|
||||||
|
err := fmt.Errorf("error %v is too small to convert", jsonAmount)
|
||||||
|
return amount, err
|
||||||
|
}
|
||||||
|
tempVal := 1e8 * jsonAmount
|
||||||
|
// So we round properly. float won't == 0 and if it did, that
|
||||||
|
// would be converted fine anyway.
|
||||||
|
if tempVal < 0 {
|
||||||
|
tempVal = tempVal - 0.5
|
||||||
|
}
|
||||||
|
if tempVal > 0 {
|
||||||
|
tempVal = tempVal + 0.5
|
||||||
|
}
|
||||||
|
// Then just rely on the integer truncating
|
||||||
|
amount = int64(tempVal)
|
||||||
|
return amount, err
|
||||||
|
}
|
395
btcjson/jsonapi_test.go
Normal file
395
btcjson/jsonapi_test.go
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cmdtests is a table of all the possible commands and a list of inputs,
|
||||||
|
// some of which should work, some of which should not (indicated by the
|
||||||
|
// pass variable). This mainly checks the type and number of the arguments,
|
||||||
|
// it does not actually check to make sure the values are correct (i.e., that
|
||||||
|
// addresses are reasonable) as the bitcoin client must be able to deal with
|
||||||
|
// that.
|
||||||
|
var cmdtests = []struct {
|
||||||
|
cmd string
|
||||||
|
args []interface{}
|
||||||
|
pass bool
|
||||||
|
}{
|
||||||
|
{"createmultisig", nil, false},
|
||||||
|
{"createmultisig", []interface{}{1}, false},
|
||||||
|
{"getinfo", nil, true},
|
||||||
|
{"getinfo", []interface{}{1}, false},
|
||||||
|
{"listaccounts", nil, true},
|
||||||
|
{"listaccounts", []interface{}{1}, true},
|
||||||
|
{"listaccounts", []interface{}{"test"}, false},
|
||||||
|
{"listaccounts", []interface{}{1, 2}, false},
|
||||||
|
{"estimatefee", nil, false},
|
||||||
|
{"estimatefee", []interface{}{1}, true},
|
||||||
|
{"estimatefee", []interface{}{1, 2}, false},
|
||||||
|
{"estimatefee", []interface{}{1.3}, false},
|
||||||
|
{"estimatepriority", nil, false},
|
||||||
|
{"estimatepriority", []interface{}{1}, true},
|
||||||
|
{"estimatepriority", []interface{}{1, 2}, false},
|
||||||
|
{"estimatepriority", []interface{}{1.3}, false},
|
||||||
|
{"getblockhash", nil, false},
|
||||||
|
{"getblockhash", []interface{}{1}, true},
|
||||||
|
{"getblockhash", []interface{}{1, 2}, false},
|
||||||
|
{"getblockhash", []interface{}{1.1}, false},
|
||||||
|
{"settxfee", nil, false},
|
||||||
|
{"settxfee", []interface{}{1.0}, true},
|
||||||
|
{"settxfee", []interface{}{1.0, 2.0}, false},
|
||||||
|
{"settxfee", []interface{}{1}, false},
|
||||||
|
{"getmemorypool", nil, true},
|
||||||
|
{"getmemorypool", []interface{}{"test"}, true},
|
||||||
|
{"getmemorypool", []interface{}{1}, false},
|
||||||
|
{"getmemorypool", []interface{}{"test", 2}, false},
|
||||||
|
{"backupwallet", nil, false},
|
||||||
|
{"backupwallet", []interface{}{1, 2}, false},
|
||||||
|
{"backupwallet", []interface{}{1}, false},
|
||||||
|
{"backupwallet", []interface{}{"testpath"}, true},
|
||||||
|
{"invalidateblock", nil, false},
|
||||||
|
{"invalidateblock", []interface{}{1, 2}, false},
|
||||||
|
{"invalidateblock", []interface{}{1}, false},
|
||||||
|
{"invalidateblock", []interface{}{"testhash"}, true},
|
||||||
|
{"reconsiderblock", nil, false},
|
||||||
|
{"reconsiderblock", []interface{}{1, 2}, false},
|
||||||
|
{"reconsiderblock", []interface{}{1}, false},
|
||||||
|
{"reconsiderblock", []interface{}{"testhash"}, true},
|
||||||
|
{"setaccount", nil, false},
|
||||||
|
{"setaccount", []interface{}{1}, false},
|
||||||
|
{"setaccount", []interface{}{1, 2, 3}, false},
|
||||||
|
{"setaccount", []interface{}{1, "test"}, false},
|
||||||
|
{"setaccount", []interface{}{"test", "test"}, true},
|
||||||
|
{"verifymessage", nil, false},
|
||||||
|
{"verifymessage", []interface{}{1}, false},
|
||||||
|
{"verifymessage", []interface{}{1, 2}, false},
|
||||||
|
{"verifymessage", []interface{}{1, 2, 3, 4}, false},
|
||||||
|
{"verifymessage", []interface{}{"test", "test", "test"}, true},
|
||||||
|
{"verifymessage", []interface{}{"test", "test", 1}, false},
|
||||||
|
{"getaddednodeinfo", nil, false},
|
||||||
|
{"getaddednodeinfo", []interface{}{1}, false},
|
||||||
|
{"getaddednodeinfo", []interface{}{true}, true},
|
||||||
|
{"getaddednodeinfo", []interface{}{true, 1}, false},
|
||||||
|
{"getaddednodeinfo", []interface{}{true, "test"}, true},
|
||||||
|
{"setgenerate", nil, false},
|
||||||
|
{"setgenerate", []interface{}{1, 2, 3}, false},
|
||||||
|
{"setgenerate", []interface{}{true}, true},
|
||||||
|
{"setgenerate", []interface{}{true, 1}, true},
|
||||||
|
{"setgenerate", []interface{}{true, 1.1}, false},
|
||||||
|
{"setgenerate", []interface{}{"true", 1}, false},
|
||||||
|
{"getbalance", nil, true},
|
||||||
|
{"getbalance", []interface{}{"test"}, true},
|
||||||
|
{"getbalance", []interface{}{"test", 1}, true},
|
||||||
|
{"getbalance", []interface{}{"test", 1.0}, false},
|
||||||
|
{"getbalance", []interface{}{1, 1}, false},
|
||||||
|
{"getbalance", []interface{}{"test", 1, 2}, false},
|
||||||
|
{"getbalance", []interface{}{1}, false},
|
||||||
|
{"addnode", nil, false},
|
||||||
|
{"addnode", []interface{}{1, 2, 3}, false},
|
||||||
|
{"addnode", []interface{}{"test", "test"}, true},
|
||||||
|
{"addnode", []interface{}{1}, false},
|
||||||
|
{"addnode", []interface{}{"test", 1}, false},
|
||||||
|
{"addnode", []interface{}{"test", 1.0}, false},
|
||||||
|
{"listreceivedbyaccount", nil, true},
|
||||||
|
{"listreceivedbyaccount", []interface{}{1, 2, 3}, false},
|
||||||
|
{"listreceivedbyaccount", []interface{}{1}, true},
|
||||||
|
{"listreceivedbyaccount", []interface{}{1.0}, false},
|
||||||
|
{"listreceivedbyaccount", []interface{}{1, false}, true},
|
||||||
|
{"listreceivedbyaccount", []interface{}{1, "false"}, false},
|
||||||
|
{"listtransactions", nil, true},
|
||||||
|
{"listtransactions", []interface{}{"test"}, true},
|
||||||
|
{"listtransactions", []interface{}{"test", 1}, true},
|
||||||
|
{"listtransactions", []interface{}{"test", 1, 2}, true},
|
||||||
|
{"listtransactions", []interface{}{"test", 1, 2, 3}, false},
|
||||||
|
{"listtransactions", []interface{}{1}, false},
|
||||||
|
{"listtransactions", []interface{}{"test", 1.0}, false},
|
||||||
|
{"listtransactions", []interface{}{"test", 1, "test"}, false},
|
||||||
|
{"importprivkey", nil, false},
|
||||||
|
{"importprivkey", []interface{}{"test"}, true},
|
||||||
|
{"importprivkey", []interface{}{1}, false},
|
||||||
|
{"importprivkey", []interface{}{"test", "test"}, true},
|
||||||
|
{"importprivkey", []interface{}{"test", "test", true}, true},
|
||||||
|
{"importprivkey", []interface{}{"test", "test", true, 1}, false},
|
||||||
|
{"importprivkey", []interface{}{"test", 1.0, true}, false},
|
||||||
|
{"importprivkey", []interface{}{"test", "test", "true"}, false},
|
||||||
|
{"listunspent", nil, true},
|
||||||
|
{"listunspent", []interface{}{1}, true},
|
||||||
|
{"listunspent", []interface{}{1, 2}, true},
|
||||||
|
{"listunspent", []interface{}{1, 2, 3}, false},
|
||||||
|
{"listunspent", []interface{}{1.0}, false},
|
||||||
|
{"listunspent", []interface{}{1, 2.0}, false},
|
||||||
|
{"sendfrom", nil, false},
|
||||||
|
{"sendfrom", []interface{}{"test"}, false},
|
||||||
|
{"sendfrom", []interface{}{"test", "test"}, false},
|
||||||
|
{"sendfrom", []interface{}{"test", "test", 1.0}, true},
|
||||||
|
{"sendfrom", []interface{}{"test", 1, 1.0}, false},
|
||||||
|
{"sendfrom", []interface{}{1, "test", 1.0}, false},
|
||||||
|
{"sendfrom", []interface{}{"test", "test", 1}, false},
|
||||||
|
{"sendfrom", []interface{}{"test", "test", 1.0, 1}, true},
|
||||||
|
{"sendfrom", []interface{}{"test", "test", 1.0, 1, "test"}, true},
|
||||||
|
{"sendfrom", []interface{}{"test", "test", 1.0, 1, "test", "test"}, true},
|
||||||
|
{"move", nil, false},
|
||||||
|
{"move", []interface{}{1, 2, 3, 4, 5, 6}, false},
|
||||||
|
{"move", []interface{}{1, 2}, false},
|
||||||
|
{"move", []interface{}{"test", "test", 1.0}, true},
|
||||||
|
{"move", []interface{}{"test", "test", 1.0, 1, "test"}, true},
|
||||||
|
{"move", []interface{}{"test", "test", 1.0, 1}, true},
|
||||||
|
{"move", []interface{}{1, "test", 1.0}, false},
|
||||||
|
{"move", []interface{}{"test", 1, 1.0}, false},
|
||||||
|
{"move", []interface{}{"test", "test", 1}, false},
|
||||||
|
{"move", []interface{}{"test", "test", 1.0, 1.0, "test"}, false},
|
||||||
|
{"move", []interface{}{"test", "test", 1.0, 1, true}, false},
|
||||||
|
{"sendtoaddress", nil, false},
|
||||||
|
{"sendtoaddress", []interface{}{"test"}, false},
|
||||||
|
{"sendtoaddress", []interface{}{"test", 1.0}, true},
|
||||||
|
{"sendtoaddress", []interface{}{"test", 1.0, "test"}, true},
|
||||||
|
{"sendtoaddress", []interface{}{"test", 1.0, "test", "test"}, true},
|
||||||
|
{"sendtoaddress", []interface{}{1, 1.0, "test", "test"}, false},
|
||||||
|
{"sendtoaddress", []interface{}{"test", 1, "test", "test"}, false},
|
||||||
|
{"sendtoaddress", []interface{}{"test", 1.0, 1.0, "test"}, false},
|
||||||
|
{"sendtoaddress", []interface{}{"test", 1.0, "test", 1.0}, false},
|
||||||
|
{"sendtoaddress", []interface{}{"test", 1.0, "test", "test", 1}, false},
|
||||||
|
{"addmultisignaddress", []interface{}{1, "test", "test"}, true},
|
||||||
|
{"addmultisignaddress", []interface{}{1, "test"}, false},
|
||||||
|
{"addmultisignaddress", []interface{}{1, 1.0, "test"}, false},
|
||||||
|
{"addmultisignaddress", []interface{}{1, "test", "test", "test"}, true},
|
||||||
|
{"createrawtransaction", []interface{}{"in1", uint32(0), "a1", 1.0}, true},
|
||||||
|
{"createrawtransaction", []interface{}{"in1", "out1", "a1", 1.0, "test"}, false},
|
||||||
|
{"createrawtransaction", []interface{}{}, false},
|
||||||
|
{"createrawtransaction", []interface{}{"in1", 1.0, "a1", 1.0}, false},
|
||||||
|
{"sendmany", []interface{}{"in1", "out1", 1.0, 1, "comment"}, true},
|
||||||
|
{"sendmany", []interface{}{"in1", "out1", 1.0, "comment"}, true},
|
||||||
|
{"sendmany", []interface{}{"in1", "out1"}, false},
|
||||||
|
{"sendmany", []interface{}{true, "out1", 1.0, 1, "comment"}, false},
|
||||||
|
{"sendmany", []interface{}{"in1", "out1", "test", 1, "comment"}, false},
|
||||||
|
{"lockunspent", []interface{}{true, "something"}, true},
|
||||||
|
{"lockunspent", []interface{}{true}, false},
|
||||||
|
{"lockunspent", []interface{}{1.0, "something"}, false},
|
||||||
|
{"signrawtransaction", []interface{}{"hexstring", "test", uint32(1), "test"}, true},
|
||||||
|
{"signrawtransaction", []interface{}{"hexstring", "test", "test2", "test3", "test4"}, false},
|
||||||
|
{"signrawtransaction", []interface{}{"hexstring", "test", "test2", "test3"}, false},
|
||||||
|
{"signrawtransaction", []interface{}{1.2, "test", "test2", "test3", "test4"}, false},
|
||||||
|
{"signrawtransaction", []interface{}{"hexstring", 1, "test2", "test3", "test4"}, false},
|
||||||
|
{"signrawtransaction", []interface{}{"hexstring", "test", "test2", 3, "test4"}, false},
|
||||||
|
{"searchrawtransactions", []interface{}{"someaddr"}, true},
|
||||||
|
{"searchrawtransactions", []interface{}{"someaddr", 1}, true},
|
||||||
|
{"searchrawtransactions", []interface{}{"someaddr", 0, 1}, true},
|
||||||
|
{"searchrawtransactions", []interface{}{"someaddr", 1, 5, 500}, true},
|
||||||
|
{"searchrawtransactions", []interface{}{"someaddr", 1, 5, "test"}, false},
|
||||||
|
{"searchrawtransactions", []interface{}{}, false},
|
||||||
|
{"listsinceblock", []interface{}{"test", "test"}, true},
|
||||||
|
{"listsinceblock", []interface{}{"test", "test", "test"}, false},
|
||||||
|
{"listsinceblock", []interface{}{"test"}, true},
|
||||||
|
{"listsinceblock", []interface{}{}, true},
|
||||||
|
{"listsinceblock", []interface{}{1, "test"}, false},
|
||||||
|
{"walletpassphrase", []interface{}{"test", 1}, true},
|
||||||
|
{"walletpassphrase", []interface{}{"test"}, false},
|
||||||
|
{"walletpassphrase", []interface{}{"test", "test"}, false},
|
||||||
|
{"getrawchangeaddress", []interface{}{}, true},
|
||||||
|
{"getrawchangeaddress", []interface{}{"something"}, true},
|
||||||
|
{"getrawchangeaddress", []interface{}{"something", "test"}, false},
|
||||||
|
{"getbestblockhash", []interface{}{}, true},
|
||||||
|
{"getbestblockhash", []interface{}{"something"}, false},
|
||||||
|
{"getblockchaininfo", []interface{}{}, true},
|
||||||
|
{"getblockchaininfo", []interface{}{"something"}, false},
|
||||||
|
{"getnetworkinfo", []interface{}{}, true},
|
||||||
|
{"getnetworkinfo", []interface{}{"something"}, false},
|
||||||
|
{"submitblock", []interface{}{}, false},
|
||||||
|
{"submitblock", []interface{}{"something"}, true},
|
||||||
|
{"submitblock", []interface{}{"something", "something else"}, true},
|
||||||
|
{"submitblock", []interface{}{"something", "something else", "more"}, false},
|
||||||
|
{"submitblock", []interface{}{"something", 1}, false},
|
||||||
|
{"fakecommand", nil, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRpcCreateMessage tests CreateMessage using the table of messages
|
||||||
|
// in cmdtests.
|
||||||
|
func TestRpcCreateMessage(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
for i, tt := range cmdtests {
|
||||||
|
if tt.args == nil {
|
||||||
|
_, err = btcjson.CreateMessage(tt.cmd)
|
||||||
|
} else {
|
||||||
|
_, err = btcjson.CreateMessage(tt.cmd, tt.args...)
|
||||||
|
}
|
||||||
|
if tt.pass {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Could not create command %d: %s %v.", i, tt.cmd, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Should create command. %d: %s", i, tt.cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRpcCommand tests RpcCommand by generating some commands and
|
||||||
|
// trying to send them off.
|
||||||
|
func TestRpcCommand(t *testing.T) {
|
||||||
|
user := "something"
|
||||||
|
pass := "something"
|
||||||
|
server := "invalid"
|
||||||
|
var msg []byte
|
||||||
|
_, err := btcjson.RpcCommand(user, pass, server, msg)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Should fail.")
|
||||||
|
}
|
||||||
|
msg, err = btcjson.CreateMessage("getinfo")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot create valid json message")
|
||||||
|
}
|
||||||
|
_, err = btcjson.RpcCommand(user, pass, server, msg)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Should not connect to server.")
|
||||||
|
}
|
||||||
|
|
||||||
|
badMsg := []byte("{\"jsonrpc\":\"1.0\",\"id\":\"btcd\",\"method\":\"\"}")
|
||||||
|
_, err = btcjson.RpcCommand(user, pass, server, badMsg)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Cannot have no method in msg..")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailingReadClose is a type used for testing so we can get something that
|
||||||
|
// fails past Go's type system.
|
||||||
|
type FailingReadCloser struct{}
|
||||||
|
|
||||||
|
func (f *FailingReadCloser) Close() error {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FailingReadCloser) Read(p []byte) (n int, err error) {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRpcReply tests JsonGetRaw by sending both a good and a bad buffer
|
||||||
|
// to it.
|
||||||
|
func TestRpcReply(t *testing.T) {
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
buffer2 := ioutil.NopCloser(buffer)
|
||||||
|
_, err := btcjson.GetRaw(buffer2)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error reading rpc reply.")
|
||||||
|
}
|
||||||
|
failBuf := &FailingReadCloser{}
|
||||||
|
_, err = btcjson.GetRaw(failBuf)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Error, this should fail.")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var idtests = []struct {
|
||||||
|
testID []interface{}
|
||||||
|
pass bool
|
||||||
|
}{
|
||||||
|
{[]interface{}{"string test"}, true},
|
||||||
|
{[]interface{}{1}, true},
|
||||||
|
{[]interface{}{1.0}, true},
|
||||||
|
{[]interface{}{nil}, true},
|
||||||
|
{[]interface{}{make(chan int)}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIsValidIdType tests that IsValidIdType allows (and disallows the correct
|
||||||
|
// types).
|
||||||
|
func TestIsValidIdType(t *testing.T) {
|
||||||
|
for _, tt := range idtests {
|
||||||
|
res := btcjson.IsValidIdType(tt.testID[0])
|
||||||
|
if res != tt.pass {
|
||||||
|
t.Errorf("Incorrect type result %v.", tt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var floattests = []struct {
|
||||||
|
in float64
|
||||||
|
out int64
|
||||||
|
pass bool
|
||||||
|
}{
|
||||||
|
{1.0, 100000000, true},
|
||||||
|
{-1.0, -100000000, true},
|
||||||
|
{0.0, 0, true},
|
||||||
|
{0.00000001, 1, true},
|
||||||
|
{-0.00000001, -1, true},
|
||||||
|
{-1.0e307, 0, false},
|
||||||
|
{1.0e307, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestJSONtoAmount tests that JSONtoAmount returns the proper values.
|
||||||
|
func TestJSONtoAmount(t *testing.T) {
|
||||||
|
for _, tt := range floattests {
|
||||||
|
res, err := btcjson.JSONToAmount(tt.in)
|
||||||
|
if tt.pass {
|
||||||
|
if res != tt.out || err != nil {
|
||||||
|
t.Errorf("Should not fail: %v", tt.in)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Should not pass: %v", tt.in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestErrorInterface tests that the Error type satisifies the builtin
|
||||||
|
// error interface and tests that the error string is created in the form
|
||||||
|
// "code: message".
|
||||||
|
func TestErrorInterface(t *testing.T) {
|
||||||
|
codes := []int{
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
}
|
||||||
|
messages := []string{
|
||||||
|
"parse error",
|
||||||
|
"error getting field",
|
||||||
|
"method not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an Error and check that both Error and *Error can be used
|
||||||
|
// as an error.
|
||||||
|
var jsonError btcjson.Error
|
||||||
|
var iface interface{} = jsonError
|
||||||
|
var ifacep interface{} = &jsonError
|
||||||
|
if _, ok := iface.(error); !ok {
|
||||||
|
t.Error("cannot type assert Error as error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := ifacep.(error); !ok {
|
||||||
|
t.Error("cannot type assert *Error as error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify jsonError is converted to the expected string using a few
|
||||||
|
// combinations of codes and messages.
|
||||||
|
for _, code := range codes {
|
||||||
|
for _, message := range messages {
|
||||||
|
// Create Error
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := fmt.Sprintf("%d: %s", jsonError.Code, jsonError.Message)
|
||||||
|
res := fmt.Sprintf("%v", jsonError)
|
||||||
|
if exp != res {
|
||||||
|
t.Errorf("error string '%s' differs from expected '%v'", res, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7283
btcjson/jsoncmd.go
Normal file
7283
btcjson/jsoncmd.go
Normal file
File diff suppressed because it is too large
Load diff
1814
btcjson/jsoncmd_test.go
Normal file
1814
btcjson/jsoncmd_test.go
Normal file
File diff suppressed because it is too large
Load diff
175
btcjson/jsonerr.go
Normal file
175
btcjson/jsonerr.go
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
// Standard JSON-RPC 2.0 errors
|
||||||
|
var (
|
||||||
|
ErrInvalidRequest = Error{
|
||||||
|
Code: -32600,
|
||||||
|
Message: "Invalid request",
|
||||||
|
}
|
||||||
|
ErrMethodNotFound = Error{
|
||||||
|
Code: -32601,
|
||||||
|
Message: "Method not found",
|
||||||
|
}
|
||||||
|
ErrInvalidParams = Error{
|
||||||
|
Code: -32602,
|
||||||
|
Message: "Invalid paramaters",
|
||||||
|
}
|
||||||
|
ErrInternal = Error{
|
||||||
|
Code: -32603,
|
||||||
|
Message: "Internal error",
|
||||||
|
}
|
||||||
|
ErrParse = Error{
|
||||||
|
Code: -32700,
|
||||||
|
Message: "Parse error",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// General application defined JSON errors
|
||||||
|
var (
|
||||||
|
ErrMisc = Error{
|
||||||
|
Code: -1,
|
||||||
|
Message: "Miscellaneous error",
|
||||||
|
}
|
||||||
|
ErrForbiddenBySafeMode = Error{
|
||||||
|
Code: -2,
|
||||||
|
Message: "Server is in safe mode, and command is not allowed in safe mode",
|
||||||
|
}
|
||||||
|
ErrType = Error{
|
||||||
|
Code: -3,
|
||||||
|
Message: "Unexpected type was passed as parameter",
|
||||||
|
}
|
||||||
|
ErrInvalidAddressOrKey = Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Invalid address or key",
|
||||||
|
}
|
||||||
|
ErrOutOfMemory = Error{
|
||||||
|
Code: -7,
|
||||||
|
Message: "Ran out of memory during operation",
|
||||||
|
}
|
||||||
|
ErrInvalidParameter = Error{
|
||||||
|
Code: -8,
|
||||||
|
Message: "Invalid, missing or duplicate parameter",
|
||||||
|
}
|
||||||
|
ErrDatabase = Error{
|
||||||
|
Code: -20,
|
||||||
|
Message: "Database error",
|
||||||
|
}
|
||||||
|
ErrDeserialization = Error{
|
||||||
|
Code: -22,
|
||||||
|
Message: "Error parsing or validating structure in raw format",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Peer-to-peer client errors
|
||||||
|
var (
|
||||||
|
ErrClientNotConnected = Error{
|
||||||
|
Code: -9,
|
||||||
|
Message: "Bitcoin is not connected",
|
||||||
|
}
|
||||||
|
ErrClientInInitialDownload = Error{
|
||||||
|
Code: -10,
|
||||||
|
Message: "Bitcoin is downloading blocks...",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wallet JSON errors
|
||||||
|
var (
|
||||||
|
ErrWallet = Error{
|
||||||
|
Code: -4,
|
||||||
|
Message: "Unspecified problem with wallet",
|
||||||
|
}
|
||||||
|
ErrWalletInsufficientFunds = Error{
|
||||||
|
Code: -6,
|
||||||
|
Message: "Not enough funds in wallet or account",
|
||||||
|
}
|
||||||
|
ErrWalletInvalidAccountName = Error{
|
||||||
|
Code: -11,
|
||||||
|
Message: "Invalid account name",
|
||||||
|
}
|
||||||
|
ErrWalletKeypoolRanOut = Error{
|
||||||
|
Code: -12,
|
||||||
|
Message: "Keypool ran out, call keypoolrefill first",
|
||||||
|
}
|
||||||
|
ErrWalletUnlockNeeded = Error{
|
||||||
|
Code: -13,
|
||||||
|
Message: "Enter the wallet passphrase with walletpassphrase first",
|
||||||
|
}
|
||||||
|
ErrWalletPassphraseIncorrect = Error{
|
||||||
|
Code: -14,
|
||||||
|
Message: "The wallet passphrase entered was incorrect",
|
||||||
|
}
|
||||||
|
ErrWalletWrongEncState = Error{
|
||||||
|
Code: -15,
|
||||||
|
Message: "Command given in wrong wallet encryption state",
|
||||||
|
}
|
||||||
|
ErrWalletEncryptionFailed = Error{
|
||||||
|
Code: -16,
|
||||||
|
Message: "Failed to encrypt the wallet",
|
||||||
|
}
|
||||||
|
ErrWalletAlreadyUnlocked = Error{
|
||||||
|
Code: -17,
|
||||||
|
Message: "Wallet is already unlocked",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Specific Errors related to commands. These are the ones a user of the rpc
|
||||||
|
// server are most likely to see. Generally, the codes should match one of the
|
||||||
|
// more general errors above.
|
||||||
|
var (
|
||||||
|
ErrBlockNotFound = Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Block not found",
|
||||||
|
}
|
||||||
|
ErrBlockCount = Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Error getting block count",
|
||||||
|
}
|
||||||
|
ErrBestBlockHash = Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Error getting best block hash",
|
||||||
|
}
|
||||||
|
ErrDifficulty = Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Error getting difficulty",
|
||||||
|
}
|
||||||
|
ErrOutOfRange = Error{
|
||||||
|
Code: -1,
|
||||||
|
Message: "Block number out of range",
|
||||||
|
}
|
||||||
|
ErrNoTxInfo = Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "No information available about transaction",
|
||||||
|
}
|
||||||
|
ErrNoNewestBlockInfo = Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "No information about newest block",
|
||||||
|
}
|
||||||
|
ErrInvalidTxVout = Error{
|
||||||
|
Code: -5,
|
||||||
|
Message: "Ouput index number (vout) does not exist for transaction.",
|
||||||
|
}
|
||||||
|
ErrRawTxString = Error{
|
||||||
|
Code: -32602,
|
||||||
|
Message: "Raw tx is not a string",
|
||||||
|
}
|
||||||
|
ErrDecodeHexString = Error{
|
||||||
|
Code: -22,
|
||||||
|
Message: "Unable to decode hex string",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errors that are specific to btcd.
|
||||||
|
var (
|
||||||
|
ErrNoWallet = Error{
|
||||||
|
Code: -1,
|
||||||
|
Message: "This implementation does not implement wallet commands",
|
||||||
|
}
|
||||||
|
ErrUnimplemented = Error{
|
||||||
|
Code: -1,
|
||||||
|
Message: "Command unimplemented",
|
||||||
|
}
|
||||||
|
)
|
79
btcjson/jsonfxns.go
Normal file
79
btcjson/jsonfxns.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
// Need to import this size it registers hash we need.
|
||||||
|
_ "crypto/sha512"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshallAndSend takes the reply structure, marshalls it to json, and
|
||||||
|
// sends it back to the io.Writer (most likely an http.ResponseWriter).
|
||||||
|
// returning a log message and an error if there is one.
|
||||||
|
func MarshallAndSend(rawReply Reply, w io.Writer) (string, error) {
|
||||||
|
finalReply, err := json.Marshal(rawReply)
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintf("[RPCS] Error Marshalling reply: %v", err)
|
||||||
|
return msg, err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s\n", finalReply)
|
||||||
|
msg := fmt.Sprintf("[RPCS] reply: %v", rawReply)
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonRPCSend connects to the daemon with the specified username, password,
|
||||||
|
// and ip/port and then send the supplied message. This uses net/http rather
|
||||||
|
// than net/rpc/jsonrpc since that one doesn't support http connections and is
|
||||||
|
// therefore useless.
|
||||||
|
func jsonRPCSend(user string, password string, server string, message []byte,
|
||||||
|
https bool, certificates []byte, skipverify bool) (*http.Response, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
protocol := "http"
|
||||||
|
if https {
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
pool.AppendCertsFromPEM(certificates)
|
||||||
|
|
||||||
|
config := &tls.Config{
|
||||||
|
InsecureSkipVerify: skipverify,
|
||||||
|
RootCAs: pool,
|
||||||
|
}
|
||||||
|
transport := &http.Transport{TLSClientConfig: config}
|
||||||
|
client.Transport = transport
|
||||||
|
protocol = "https"
|
||||||
|
}
|
||||||
|
credentials := url.UserPassword(user, password).String()
|
||||||
|
resp, err := client.Post(protocol+"://"+credentials+"@"+server,
|
||||||
|
"application/json", bytes.NewReader(message))
|
||||||
|
if err != nil {
|
||||||
|
// We do not want to log the username/password in the errors.
|
||||||
|
replaceStr := "<username>:<password>"
|
||||||
|
str := strings.Replace(err.Error(), credentials, replaceStr, -1)
|
||||||
|
err = fmt.Errorf("%v", str)
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRaw should be called after JsonRpcSend. It reads and returns
|
||||||
|
// the reply (which you can then call ReadResultCmd() on) and closes the
|
||||||
|
// connection.
|
||||||
|
func GetRaw(resp io.ReadCloser) ([]byte, error) {
|
||||||
|
body, err := ioutil.ReadAll(resp)
|
||||||
|
resp.Close()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error reading json reply: %v", err)
|
||||||
|
return body, err
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
56
btcjson/jsonfxns_test.go
Normal file
56
btcjson/jsonfxns_test.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMarshallAndSend tests the MarshallAndSend function to make sure it can
|
||||||
|
// create a json message to write to the io.Writerr and to make sure
|
||||||
|
// it fails properly in cases where it cannot generate json.
|
||||||
|
func TestMarshallAndSend(t *testing.T) {
|
||||||
|
jsonError := btcjson.Error{
|
||||||
|
Code: -32700,
|
||||||
|
Message: "Parse error",
|
||||||
|
}
|
||||||
|
// json.Marshal cannot handle channels so this is a good way to get a
|
||||||
|
// marshal failure.
|
||||||
|
badRes := make(chan interface{})
|
||||||
|
rawReply := btcjson.Reply{
|
||||||
|
Result: badRes,
|
||||||
|
Error: &jsonError,
|
||||||
|
Id: nil,
|
||||||
|
}
|
||||||
|
var w bytes.Buffer
|
||||||
|
|
||||||
|
msg, err := btcjson.MarshallAndSend(rawReply, &w)
|
||||||
|
if fmt.Sprintf("%s", err) != "json: unsupported type: chan interface {}" {
|
||||||
|
t.Error("Should not be able to unmarshall channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use something simple so we can compare the reply.
|
||||||
|
rawReply = btcjson.Reply{
|
||||||
|
Result: nil,
|
||||||
|
Error: nil,
|
||||||
|
Id: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err = btcjson.MarshallAndSend(rawReply, &w)
|
||||||
|
if msg != "[RPCS] reply: {<nil> <nil> <nil>}" {
|
||||||
|
t.Error("Incorrect reply:", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
expBuf := "{\"result\":null,\"error\":null,\"id\":null}\n"
|
||||||
|
|
||||||
|
if w.String() != expBuf {
|
||||||
|
t.Error("Incorrect data in buffer:", w.String())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
748
btcjson/jsonresults.go
Normal file
748
btcjson/jsonresults.go
Normal file
|
@ -0,0 +1,748 @@
|
||||||
|
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockResult models the data from the getblock command when the verbose flag
|
||||||
|
// is set. When the verbose flag is not set, getblock return a hex-encoded
|
||||||
|
// string.
|
||||||
|
type BlockResult struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Confirmations uint64 `json:"confirmations"`
|
||||||
|
Size int32 `json:"size"`
|
||||||
|
Height int64 `json:"height"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
MerkleRoot string `json:"merkleroot"`
|
||||||
|
Tx []string `json:"tx,omitempty"`
|
||||||
|
RawTx []TxRawResult `json:"rawtx,omitempty"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
Nonce uint32 `json:"nonce"`
|
||||||
|
Bits string `json:"bits"`
|
||||||
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
PreviousHash string `json:"previousblockhash"`
|
||||||
|
NextHash string `json:"nextblockhash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMultiSigResult models the data returned from the createmultisig command.
|
||||||
|
type CreateMultiSigResult struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
RedeemScript string `json:"redeemScript"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeScriptResult models the data returned from the decodescript command.
|
||||||
|
type DecodeScriptResult struct {
|
||||||
|
Asm string `json:"asm"`
|
||||||
|
ReqSigs int32 `json:"reqSigs,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Addresses []string `json:"addresses,omitempty"`
|
||||||
|
P2sh string `json:"p2sh"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddedNodeInfoResultAddr models the data of the addresses portion of the
|
||||||
|
// getaddednodeinfo command.
|
||||||
|
type GetAddedNodeInfoResultAddr struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Connected string `json:"connected"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddedNodeInfoResult models the data from the getaddednodeinfo command.
|
||||||
|
type GetAddedNodeInfoResult struct {
|
||||||
|
AddedNode string `json:"addednode"`
|
||||||
|
Connected *bool `json:"connected,omitempty"`
|
||||||
|
Addresses *[]GetAddedNodeInfoResultAddr `json:"addresses,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockChainInfoResult models the data returned from the getblockchaininfo
|
||||||
|
// command.
|
||||||
|
type GetBlockChainInfoResult struct {
|
||||||
|
Chain string `json:"chain"`
|
||||||
|
Blocks int32 `json:"blocks"`
|
||||||
|
BestBlockHash string `json:"bestblockhash"`
|
||||||
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
VerificationProgress float64 `json:"verificationprogress"`
|
||||||
|
ChainWork string `json:"chainwork"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTemplateResultTx models the transactions field of the
|
||||||
|
// getblocktemplate command.
|
||||||
|
type GetBlockTemplateResultTx struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Depends []int64 `json:"depends"`
|
||||||
|
Fee int64 `json:"fee"`
|
||||||
|
SigOps int64 `json:"sigops"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTemplateResultAux models the coinbaseaux field of the
|
||||||
|
// getblocktemplate command.
|
||||||
|
type GetBlockTemplateResultAux struct {
|
||||||
|
Flags string `json:"flags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTemplateResult models the data returned from the getblocktemplate
|
||||||
|
// command.
|
||||||
|
type GetBlockTemplateResult struct {
|
||||||
|
// Base fields from BIP 0022. CoinbaseAux is optional. One of
|
||||||
|
// CoinbaseTxn or CoinbaseValue must be specified, but not both.
|
||||||
|
Bits string `json:"bits"`
|
||||||
|
CurTime int64 `json:"curtime"`
|
||||||
|
Height int64 `json:"height"`
|
||||||
|
PreviousHash string `json:"previousblockhash"`
|
||||||
|
SigOpLimit int64 `json:"sigoplimit,omitempty"`
|
||||||
|
SizeLimit int64 `json:"sizelimit,omitempty"`
|
||||||
|
Transactions []GetBlockTemplateResultTx `json:"transactions"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
CoinbaseAux *GetBlockTemplateResultAux `json:"coinbaseaux,omitempty"`
|
||||||
|
CoinbaseTxn *GetBlockTemplateResultTx `json:"coinbasetxn,omitempty"`
|
||||||
|
CoinbaseValue *int64 `json:"coinbasevalue,omitempty"`
|
||||||
|
WorkID string `json:"workid,omitempty"`
|
||||||
|
|
||||||
|
// Optional long polling from BIP 0022.
|
||||||
|
LongPollID string `json:"longpollid,omitempty"`
|
||||||
|
LongPollURI string `json:"longpolluri,omitempty"`
|
||||||
|
SubmitOld *bool `json:"submitold,omitempty"`
|
||||||
|
|
||||||
|
// Basic pool extension from BIP 0023.
|
||||||
|
Target string `json:"target,omitempty"`
|
||||||
|
Expires int64 `json:"expires,omitempty"`
|
||||||
|
|
||||||
|
// Mutations from BIP 0023.
|
||||||
|
MaxTime int64 `json:"maxtime,omitempty"`
|
||||||
|
MinTime int64 `json:"mintime,omitempty"`
|
||||||
|
Mutable []string `json:"mutable,omitempty"`
|
||||||
|
NonceRange string `json:"noncerange,omitempty"`
|
||||||
|
|
||||||
|
// Block proposal from BIP 0023.
|
||||||
|
Capabilities []string `json:"capabilities,omitempty"`
|
||||||
|
RejectReasion string `json:"reject-reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkInfoResult models the data returned from the getnetworkinfo command.
|
||||||
|
type GetNetworkInfoResult struct {
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
ProtocolVersion int32 `json:"protocolversion"`
|
||||||
|
TimeOffset int64 `json:"timeoffset"`
|
||||||
|
Connections int32 `json:"connections"`
|
||||||
|
Networks []NetworksResult `json:"networks"`
|
||||||
|
RelayFee float64 `json:"relayfee"`
|
||||||
|
LocalAddresses []LocalAddressesResult `json:"localaddresses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeerInfoResult models the data returned from the getpeerinfo command.
|
||||||
|
type GetPeerInfoResult struct {
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
AddrLocal string `json:"addrlocal,omitempty"`
|
||||||
|
Services string `json:"services"`
|
||||||
|
LastSend int64 `json:"lastsend"`
|
||||||
|
LastRecv int64 `json:"lastrecv"`
|
||||||
|
BytesSent uint64 `json:"bytessent"`
|
||||||
|
BytesRecv uint64 `json:"bytesrecv"`
|
||||||
|
PingTime float64 `json:"pingtime"`
|
||||||
|
PingWait float64 `json:"pingwait,omitempty"`
|
||||||
|
ConnTime int64 `json:"conntime"`
|
||||||
|
Version uint32 `json:"version"`
|
||||||
|
SubVer string `json:"subver"`
|
||||||
|
Inbound bool `json:"inbound"`
|
||||||
|
StartingHeight int32 `json:"startingheight"`
|
||||||
|
CurrentHeight int32 `json:"currentheight,omitempty"`
|
||||||
|
BanScore int32 `json:"banscore,omitempty"`
|
||||||
|
SyncNode bool `json:"syncnode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawMempoolResult models the data returned from the getrawmempool command.
|
||||||
|
type GetRawMempoolResult struct {
|
||||||
|
Size int32 `json:"size"`
|
||||||
|
Fee float64 `json:"fee"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
Height int64 `json:"height"`
|
||||||
|
StartingPriority float64 `json:"startingpriority"`
|
||||||
|
CurrentPriority float64 `json:"currentpriority"`
|
||||||
|
Depends []string `json:"depends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionDetailsResult models the details data from the gettransaction command.
|
||||||
|
type GetTransactionDetailsResult struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Fee float64 `json:"fee,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionResult models the data from the gettransaction command.
|
||||||
|
type GetTransactionResult struct {
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Fee float64 `json:"fee,omitempty"`
|
||||||
|
Confirmations int64 `json:"confirmations"`
|
||||||
|
BlockHash string `json:"blockhash"`
|
||||||
|
BlockIndex int64 `json:"blockindex"`
|
||||||
|
BlockTime int64 `json:"blocktime"`
|
||||||
|
TxID string `json:"txid"`
|
||||||
|
WalletConflicts []string `json:"walletconflicts"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
TimeReceived int64 `json:"timereceived"`
|
||||||
|
Details []GetTransactionDetailsResult `json:"details"`
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxOutResult models the data from the gettxout command.
|
||||||
|
type GetTxOutResult struct {
|
||||||
|
BestBlock string `json:"bestblock"`
|
||||||
|
Confirmations int64 `json:"confirmations"`
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
Coinbase bool `json:"coinbase"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetTotalsResult models the data returned from the getnettotals command.
|
||||||
|
type GetNetTotalsResult struct {
|
||||||
|
TotalBytesRecv uint64 `json:"totalbytesrecv"`
|
||||||
|
TotalBytesSent uint64 `json:"totalbytessent"`
|
||||||
|
TimeMillis int64 `json:"timemillis"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptSig models a signature script. It is defined seperately since it only
|
||||||
|
// applies to non-coinbase. Therefore the field in the Vin structure needs
|
||||||
|
// to be a pointer.
|
||||||
|
type ScriptSig struct {
|
||||||
|
Asm string `json:"asm"`
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vin models parts of the tx data. It is defined seperately since both
|
||||||
|
// getrawtransaction, sendrawtransaction, and decoderawtransaction use the
|
||||||
|
// same structure.
|
||||||
|
type Vin struct {
|
||||||
|
Coinbase string `json:"coinbase"`
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||||
|
Sequence uint32 `json:"sequence"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCoinBase returns a bool to show if a Vin is a Coinbase one or not.
|
||||||
|
func (v *Vin) IsCoinBase() bool {
|
||||||
|
return len(v.Coinbase) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON provides a custom Marshal method for Vin.
|
||||||
|
func (v *Vin) MarshalJSON() ([]byte, error) {
|
||||||
|
if v.IsCoinBase() {
|
||||||
|
coinbaseStruct := struct {
|
||||||
|
Coinbase string `json:"coinbase"`
|
||||||
|
Sequence uint32 `json:"sequence"`
|
||||||
|
}{
|
||||||
|
Coinbase: v.Coinbase,
|
||||||
|
Sequence: v.Sequence,
|
||||||
|
}
|
||||||
|
return json.Marshal(coinbaseStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
txStruct := struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||||
|
Sequence uint32 `json:"sequence"`
|
||||||
|
}{
|
||||||
|
Txid: v.Txid,
|
||||||
|
Vout: v.Vout,
|
||||||
|
ScriptSig: v.ScriptSig,
|
||||||
|
Sequence: v.Sequence,
|
||||||
|
}
|
||||||
|
return json.Marshal(txStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptPubKeyResult models the scriptPubKey data of a tx script. It is
|
||||||
|
// defined separately since it is used by multiple commands.
|
||||||
|
type ScriptPubKeyResult struct {
|
||||||
|
Asm string `json:"asm"`
|
||||||
|
Hex string `json:"hex,omitempty"`
|
||||||
|
ReqSigs int32 `json:"reqSigs,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Addresses []string `json:"addresses,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vout models parts of the tx data. It is defined seperately since both
|
||||||
|
// getrawtransaction, sendrawtransaction, and decoderawtransaction use the same
|
||||||
|
// structure.
|
||||||
|
type Vout struct {
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
N uint32 `json:"n"`
|
||||||
|
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMiningInfoResult models the data from the getmininginfo command.
|
||||||
|
type GetMiningInfoResult struct {
|
||||||
|
Blocks int64 `json:"blocks"`
|
||||||
|
CurrentBlockSize uint64 `json:"currentblocksize"`
|
||||||
|
CurrentBlockTx uint64 `json:"currentblocktx"`
|
||||||
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
Errors string `json:"errors"`
|
||||||
|
Generate bool `json:"generate"`
|
||||||
|
GenProcLimit int32 `json:"genproclimit"`
|
||||||
|
HashesPerSec int64 `json:"hashespersec"`
|
||||||
|
NetworkHashPS int64 `json:"networkhashps"`
|
||||||
|
PooledTx uint64 `json:"pooledtx"`
|
||||||
|
TestNet bool `json:"testnet"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWorkResult models the data from the getwork command.
|
||||||
|
type GetWorkResult struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
Hash1 string `json:"hash1"`
|
||||||
|
Midstate string `json:"midstate"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoResult contains the data returned by the getinfo command.
|
||||||
|
type InfoResult struct {
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
ProtocolVersion int32 `json:"protocolversion"`
|
||||||
|
WalletVersion int32 `json:"walletversion,omitempty"`
|
||||||
|
Balance float64 `json:"balance,omitempty"`
|
||||||
|
Blocks int32 `json:"blocks"`
|
||||||
|
TimeOffset int64 `json:"timeoffset"`
|
||||||
|
Connections int32 `json:"connections"`
|
||||||
|
Proxy string `json:"proxy"`
|
||||||
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
TestNet bool `json:"testnet"`
|
||||||
|
KeypoolOldest int64 `json:"keypoololdest,omitempty"`
|
||||||
|
KeypoolSize int32 `json:"keypoolsize,omitempty"`
|
||||||
|
UnlockedUntil int64 `json:"unlocked_until,omitempty"`
|
||||||
|
PaytxFee float64 `json:"paytxfee,omitempty"`
|
||||||
|
RelayFee float64 `json:"relayfee"`
|
||||||
|
Errors string `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTransactionsResult models the data from the listtransactions command.
|
||||||
|
type ListTransactionsResult struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Fee float64 `json:"fee"`
|
||||||
|
Confirmations int64 `json:"confirmations"`
|
||||||
|
Generated bool `json:"generated,omitempty"`
|
||||||
|
BlockHash string `json:"blockhash,omitempty"`
|
||||||
|
BlockIndex int64 `json:"blockindex,omitempty"`
|
||||||
|
BlockTime int64 `json:"blocktime,omitempty"`
|
||||||
|
TxID string `json:"txid"`
|
||||||
|
WalletConflicts []string `json:"walletconflicts"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
TimeReceived int64 `json:"timereceived"`
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
OtherAccount string `json:"otheraccount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddressesResult models the localaddresses data from the getnetworkinfo command.
|
||||||
|
type LocalAddressesResult struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Score int32 `json:"score"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReceivedByAccountResult models the data from the listreceivedbyaccount
|
||||||
|
// command.
|
||||||
|
type ListReceivedByAccountResult struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Confirmations uint64 `json:"confirmations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReceivedByAddressResult models the data from the listreceivedbyaddress
|
||||||
|
// command.
|
||||||
|
type ListReceivedByAddressResult struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Confirmations uint64 `json:"confirmations"`
|
||||||
|
TxIDs []string `json:"txids,omitempty"`
|
||||||
|
InvolvesWatchonly bool `json:"involvesWatchonly,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSinceBlockResult models the data from the listsinceblock command.
|
||||||
|
type ListSinceBlockResult struct {
|
||||||
|
Transactions []ListTransactionsResult `json:"transactions"`
|
||||||
|
LastBlock string `json:"lastblock"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUnspentResult models a successful response from the listunspent request.
|
||||||
|
type ListUnspentResult struct {
|
||||||
|
TxId string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Account string `json:"account"`
|
||||||
|
ScriptPubKey string `json:"scriptPubKey"`
|
||||||
|
RedeemScript string `json:"redeemScript,omitempty"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Confirmations int64 `json:"confirmations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworksResult models the networks data from the getnetworkinfo command.
|
||||||
|
type NetworksResult struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Limited bool `json:"limited"`
|
||||||
|
Reachable bool `json:"reachable"`
|
||||||
|
Proxy string `json:"proxy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignRawTransactionResult models the data from the signrawtransaction
|
||||||
|
// command.
|
||||||
|
type SignRawTransactionResult struct {
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
Complete bool `json:"complete"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxRawResult models the data from the getrawtransaction and sendrawtransaction
|
||||||
|
// commands
|
||||||
|
type TxRawResult struct {
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
LockTime uint32 `json:"locktime"`
|
||||||
|
Vin []Vin `json:"vin"`
|
||||||
|
Vout []Vout `json:"vout"`
|
||||||
|
BlockHash string `json:"blockhash,omitempty"`
|
||||||
|
Confirmations uint64 `json:"confirmations"`
|
||||||
|
Time int64 `json:"time,omitempty"`
|
||||||
|
Blocktime int64 `json:"blocktime,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxRawDecodeResult models the data from the decoderawtransaction command.
|
||||||
|
type TxRawDecodeResult struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
Locktime uint32 `json:"locktime"`
|
||||||
|
Vin []Vin `json:"vin"`
|
||||||
|
Vout []Vout `json:"vout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAddressResult models the data from the validateaddress command.
|
||||||
|
type ValidateAddressResult struct {
|
||||||
|
IsValid bool `json:"isvalid"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
IsMine bool `json:"ismine,omitempty"`
|
||||||
|
IsWatchOnly bool `json:"iswatchonly,omitempty"`
|
||||||
|
IsScript bool `json:"isscript,omitempty"`
|
||||||
|
PubKey string `json:"pubkey,omitempty"`
|
||||||
|
IsCompressed bool `json:"iscompressed,omitempty"`
|
||||||
|
Account string `json:"account,omitempty"`
|
||||||
|
Addresses []string `json:"addresses,omitempty"`
|
||||||
|
Hex string `json:"hex,omitempty"`
|
||||||
|
Script string `json:"script,omitempty"`
|
||||||
|
SigsRequired int32 `json:"sigsrequired,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadResultCmd unmarshalls the json reply with data struct for specific
|
||||||
|
// commands or an interface if it is not a command where we already have a
|
||||||
|
// struct ready.
|
||||||
|
func ReadResultCmd(cmd string, message []byte) (Reply, error) {
|
||||||
|
var result Reply
|
||||||
|
var err error
|
||||||
|
var objmap map[string]json.RawMessage
|
||||||
|
err = json.Unmarshal(message, &objmap)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
// Take care of the parts that are the same for all replies.
|
||||||
|
var jsonErr Error
|
||||||
|
var id interface{}
|
||||||
|
err = json.Unmarshal(objmap["error"], &jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error unmarshalling json reply: %v", err)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(objmap["id"], &id)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error unmarshalling json reply: %v", err)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is a command where we have already worked out the reply,
|
||||||
|
// generate put the results in the proper structure.
|
||||||
|
// We handle the error condition after the switch statement.
|
||||||
|
switch cmd {
|
||||||
|
case "createmultisig":
|
||||||
|
var res *CreateMultiSigResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "decodescript":
|
||||||
|
var res *DecodeScriptResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getaddednodeinfo":
|
||||||
|
// getaddednodeinfo can either return a JSON object or a
|
||||||
|
// slice of strings depending on the verbose flag. Choose the
|
||||||
|
// right form accordingly.
|
||||||
|
if bytes.IndexByte(objmap["result"], '{') > -1 {
|
||||||
|
var res []GetAddedNodeInfoResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var res []string
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "getinfo":
|
||||||
|
var res *InfoResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getblock":
|
||||||
|
// getblock can either return a JSON object or a hex-encoded
|
||||||
|
// string depending on the verbose flag. Choose the right form
|
||||||
|
// accordingly.
|
||||||
|
if bytes.IndexByte(objmap["result"], '{') > -1 {
|
||||||
|
var res *BlockResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var res string
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "getblockchaininfo":
|
||||||
|
var res *GetBlockChainInfoResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getnettotals":
|
||||||
|
var res *GetNetTotalsResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getnetworkhashps":
|
||||||
|
var res int64
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getpeerinfo":
|
||||||
|
var res []GetPeerInfoResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getrawtransaction":
|
||||||
|
// getrawtransaction can either return a JSON object or a
|
||||||
|
// hex-encoded string depending on the verbose flag. Choose the
|
||||||
|
// right form accordingly.
|
||||||
|
if bytes.IndexByte(objmap["result"], '{') > -1 {
|
||||||
|
var res *TxRawResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var res string
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "decoderawtransaction":
|
||||||
|
var res *TxRawDecodeResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getaddressesbyaccount":
|
||||||
|
var res []string
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getmininginfo":
|
||||||
|
var res *GetMiningInfoResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getnetworkinfo":
|
||||||
|
var res *GetNetworkInfoResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
if res != nil && res.LocalAddresses == nil {
|
||||||
|
res.LocalAddresses = []LocalAddressesResult{}
|
||||||
|
}
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getrawmempool":
|
||||||
|
// getrawmempool can either return a map of JSON objects or
|
||||||
|
// an array of strings depending on the verbose flag. Choose
|
||||||
|
// the right form accordingly.
|
||||||
|
if bytes.IndexByte(objmap["result"], '{') > -1 {
|
||||||
|
var res map[string]GetRawMempoolResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var res []string
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "gettransaction":
|
||||||
|
var res *GetTransactionResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "gettxout":
|
||||||
|
var res *GetTxOutResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if res != nil && err == nil {
|
||||||
|
if res.ScriptPubKey.Addresses == nil {
|
||||||
|
res.ScriptPubKey.Addresses = []string{}
|
||||||
|
}
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "getwork":
|
||||||
|
// getwork can either return a JSON object or a boolean
|
||||||
|
// depending on whether or not data was provided. Choose the
|
||||||
|
// right form accordingly.
|
||||||
|
if bytes.IndexByte(objmap["result"], '{') > -1 {
|
||||||
|
var res *GetWorkResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var res bool
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "validateaddress":
|
||||||
|
var res *ValidateAddressResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "signrawtransaction":
|
||||||
|
var res *SignRawTransactionResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "listaccounts":
|
||||||
|
var res map[string]float64
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "listreceivedbyaccount":
|
||||||
|
var res []ListReceivedByAccountResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "listreceivedbyaddress":
|
||||||
|
var res []ListReceivedByAddressResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "listsinceblock":
|
||||||
|
var res *ListSinceBlockResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
if res.Transactions == nil {
|
||||||
|
res.Transactions = []ListTransactionsResult{}
|
||||||
|
}
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "listtransactions":
|
||||||
|
var res []ListTransactionsResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
case "listunspent":
|
||||||
|
var res []ListUnspentResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
|
||||||
|
case "searchrawtransactions":
|
||||||
|
// searchrawtransactions can either return a list of JSON objects
|
||||||
|
// or a list of hex-encoded strings depending on the verbose flag.
|
||||||
|
// Choose the right form accordingly.
|
||||||
|
if bytes.IndexByte(objmap["result"], '{') > -1 {
|
||||||
|
var res []*TxRawResult
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var res []string
|
||||||
|
err = json.Unmarshal(objmap["result"], &res)
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// For commands that return a single item (or no items), we get it with
|
||||||
|
// the correct concrete type for free (but treat them separately
|
||||||
|
// for clarity).
|
||||||
|
case "getblockcount", "getbalance", "getblockhash", "getgenerate",
|
||||||
|
"getconnectioncount", "getdifficulty", "gethashespersec",
|
||||||
|
"setgenerate", "stop", "settxfee", "getaccount",
|
||||||
|
"getnewaddress", "sendtoaddress", "createrawtransaction",
|
||||||
|
"sendrawtransaction", "getbestblockhash", "getrawchangeaddress",
|
||||||
|
"sendfrom", "sendmany", "addmultisigaddress", "getunconfirmedbalance",
|
||||||
|
"getaccountaddress", "estimatefee", "estimatepriority":
|
||||||
|
err = json.Unmarshal(message, &result)
|
||||||
|
default:
|
||||||
|
// None of the standard Bitcoin RPC methods matched. Try
|
||||||
|
// registered custom command reply parsers.
|
||||||
|
if c, ok := customCmds[cmd]; ok && c.replyParser != nil {
|
||||||
|
var res interface{}
|
||||||
|
res, err = c.replyParser(objmap["result"])
|
||||||
|
if err == nil {
|
||||||
|
result.Result = res
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For anything else put it in an interface. All the
|
||||||
|
// data is still there, just a little less convenient
|
||||||
|
// to deal with.
|
||||||
|
err = json.Unmarshal(message, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error unmarshalling json reply: %v", err)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
// Only want the error field when there is an actual error to report.
|
||||||
|
if jsonErr.Code != 0 {
|
||||||
|
result.Error = &jsonErr
|
||||||
|
}
|
||||||
|
result.Id = &id
|
||||||
|
return result, err
|
||||||
|
}
|
99
btcjson/jsonresults_test.go
Normal file
99
btcjson/jsonresults_test.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
var resulttests = []struct {
|
||||||
|
cmd string
|
||||||
|
msg []byte
|
||||||
|
comp bool
|
||||||
|
pass bool
|
||||||
|
}{
|
||||||
|
// Generate a fake message to make sure we can encode and decode it and
|
||||||
|
// get the same thing back.
|
||||||
|
{"getblockcount",
|
||||||
|
[]byte(`{"result":226790,"error":{"code":1,"message":"No Error"},"id":"btcd"}`),
|
||||||
|
true, true},
|
||||||
|
// Generate a fake message to make sure we don't make a command from it.
|
||||||
|
{"anycommand", []byte(`{"result":"test","id":1}`), false, false},
|
||||||
|
{"anycommand", []byte(`{some junk}`), false, false},
|
||||||
|
{"anycommand", []byte(`{"error":null,"result":null,"id":"test"}`), false, true},
|
||||||
|
{"createmultisig", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"createmultisig", []byte(`{"error":null,"id":1,"result":{"address":"something","redeemScript":"else"}}`), false, true},
|
||||||
|
{"decodescript", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"decodescript", []byte(`{"error":null,"id":1,"result":{"Asm":"something"}}`), false, true},
|
||||||
|
{"getinfo", []byte(`{"error":null,"result":null,"id":"test"}`), false, true},
|
||||||
|
{"getinfo", []byte(`{"error":null,"result":null}`), false, false},
|
||||||
|
{"getinfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"getblock", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"getblock", []byte(`{"result":{"hash":"000000","confirmations":16007,"size":325648},"error":null,"id":1}`), false, true},
|
||||||
|
{"getblockchaininfo", []byte(`{"result":{"chain":"hex","blocks":250000,"bestblockhash":"hash"},"error":null,"id":1}`), false, true},
|
||||||
|
{"getblockchaininfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"getnetworkinfo", []byte(`{"result":{"version":100,"protocolversion":70002},"error":null,"id":1}`), false, true},
|
||||||
|
{"getnetworkinfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"getrawtransaction", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"getrawtransaction", []byte(`{"error":null,"id":1,"result":{"hex":"somejunk","version":1}}`), false, true},
|
||||||
|
{"gettransaction", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"gettransaction", []byte(`{"error":null,"id":1,"result":{"Amount":0.0}}`), false, true},
|
||||||
|
{"decoderawtransaction", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"decoderawtransaction", []byte(`{"error":null,"id":1,"result":{"Txid":"something"}}`), false, true},
|
||||||
|
{"getaddressesbyaccount", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"getaddressesbyaccount", []byte(`{"error":null,"id":1,"result":["test"]}`), false, true},
|
||||||
|
{"getmininginfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"getmininginfo", []byte(`{"error":null,"id":1,"result":{"generate":true}}`), false, true},
|
||||||
|
{"gettxout", []byte(`{"error":null,"id":1,"result":{"bestblock":"a","value":1.0}}`), false, true},
|
||||||
|
{"listreceivedbyaddress", []byte(`{"error":null,"id":1,"result":[{"a"}]}`), false, false},
|
||||||
|
{"listreceivedbyaddress", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, true},
|
||||||
|
{"listsinceblock", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
|
||||||
|
{"listsinceblock", []byte(`{"error":null,"id":1,"result":{"lastblock":"something"}}`), false, true},
|
||||||
|
{"validateaddress", []byte(`{"error":null,"id":1,"result":{"isvalid":false}}`), false, true},
|
||||||
|
{"validateaddress", []byte(`{"error":null,"id":1,"result":{false}}`), false, false},
|
||||||
|
{"signrawtransaction", []byte(`{"error":null,"id":1,"result":{"hex":"something","complete":false}}`), false, true},
|
||||||
|
{"signrawtransaction", []byte(`{"error":null,"id":1,"result":{false}}`), false, false},
|
||||||
|
{"listunspent", []byte(`{"error":null,"id":1,"result":[{"txid":"something"}]}`), false, true},
|
||||||
|
{"listunspent", []byte(`{"error":null,"id":1,"result":[{"txid"}]}`), false, false},
|
||||||
|
{"searchrawtransactions", []byte(`{"error":null,"id":1,"result":{"a":"b"}}`), false, false},
|
||||||
|
{"searchrawtransactions", []byte(`{"error":null,"id":1,"result":["sometxhex"]}`), false, true},
|
||||||
|
{"searchrawtransactions", []byte(`{"error":null,"id":1,"result":[{"hex":"somejunk","version":1}]}`), false, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReadResultCmd tests that readResultCmd can properly unmarshall the
|
||||||
|
// returned []byte that contains a json reply for both known and unknown
|
||||||
|
// messages.
|
||||||
|
func TestReadResultCmd(t *testing.T) {
|
||||||
|
for i, tt := range resulttests {
|
||||||
|
result, err := btcjson.ReadResultCmd(tt.cmd, tt.msg)
|
||||||
|
if tt.pass {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Should read result: %d %v", i, err)
|
||||||
|
}
|
||||||
|
// Due to the pointer for the Error and other structs,
|
||||||
|
// we can't always guarantee byte for byte comparison.
|
||||||
|
if tt.comp {
|
||||||
|
msg2, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Should unmarshal result: %d %v", i, err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(tt.msg, msg2) {
|
||||||
|
t.Errorf("json byte arrays differ. %d %v %v", i, tt.msg, msg2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Should fail: %d, %s", i, tt.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
50
btcjson/v2/btcjson/btcdextcmds.go
Normal file
50
btcjson/v2/btcjson/btcdextcmds.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||||
|
// a chain server with btcd extensions.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
// DebugLevelCmd defines the debuglevel JSON-RPC command. This command is not a
|
||||||
|
// standard Bitcoin command. It is an extension for btcd.
|
||||||
|
type DebugLevelCmd struct {
|
||||||
|
LevelSpec string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDebugLevelCmd returns a new DebugLevelCmd which can be used to issue a
|
||||||
|
// debuglevel JSON-RPC command. This command is not a standard Bitcoin command.
|
||||||
|
// It is an extension for btcd.
|
||||||
|
func NewDebugLevelCmd(levelSpec string) *DebugLevelCmd {
|
||||||
|
return &DebugLevelCmd{
|
||||||
|
LevelSpec: levelSpec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBestBlockCmd defines the getbestblock JSON-RPC command.
|
||||||
|
type GetBestBlockCmd struct{}
|
||||||
|
|
||||||
|
// NewGetBestBlockCmd returns a new instance which can be used to issue a
|
||||||
|
// getbestblock JSON-RPC command.
|
||||||
|
func NewGetBestBlockCmd() *GetBestBlockCmd {
|
||||||
|
return &GetBestBlockCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentNetCmd defines the getcurrentnet JSON-RPC command.
|
||||||
|
type GetCurrentNetCmd struct{}
|
||||||
|
|
||||||
|
// NewGetCurrentNetCmd returns a new instance which can be used to issue a
|
||||||
|
// getcurrentnet JSON-RPC command.
|
||||||
|
func NewGetCurrentNetCmd() *GetCurrentNetCmd {
|
||||||
|
return &GetCurrentNetCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// No special flags for commands in this file.
|
||||||
|
flags := UsageFlag(0)
|
||||||
|
|
||||||
|
MustRegisterCmd("debuglevel", (*DebugLevelCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getbestblock", (*GetBestBlockCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getcurrentnet", (*GetCurrentNetCmd)(nil), flags)
|
||||||
|
}
|
134
btcjson/v2/btcjson/btcdextcmds_test.go
Normal file
134
btcjson/v2/btcjson/btcdextcmds_test.go
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBtcdExtCmds tests all of the btcd extended commands marshal and unmarshal
|
||||||
|
// into valid results include handling of optional fields being omitted in the
|
||||||
|
// marshalled command, while optional fields with defaults have the default
|
||||||
|
// assigned on unmarshalled commands.
|
||||||
|
func TestBtcdExtCmds(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testID := int(1)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
newCmd func() (interface{}, error)
|
||||||
|
staticCmd func() interface{}
|
||||||
|
marshalled string
|
||||||
|
unmarshalled interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "debuglevel",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("debuglevel", "trace")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewDebugLevelCmd("trace")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"debuglevel","params":["trace"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.DebugLevelCmd{
|
||||||
|
LevelSpec: "trace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getbestblock",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getbestblock")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetBestBlockCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getbestblock","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBestBlockCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getcurrentnet",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getcurrentnet")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetCurrentNetCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getcurrentnet","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetCurrentNetCmd{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Marshal the command as created by the new static command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is created without error via the generic
|
||||||
|
// new command creation function.
|
||||||
|
cmd, err := test.newCmd()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||||
|
i, test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the command as created by the generic new command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var request btcjson.Request
|
||||||
|
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||||
|
"unmarshalling JSON-RPC request: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||||
|
"- got %s, want %s", i, test.name,
|
||||||
|
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||||
|
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
btcjson/v2/btcjson/btcwalletextcmds.go
Normal file
104
btcjson/v2/btcjson/btcwalletextcmds.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright (c) 2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||||
|
// a wallet server with btcwallet extensions.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
// CreateNewAccountCmd defines the createnewaccount JSON-RPC command.
|
||||||
|
type CreateNewAccountCmd struct {
|
||||||
|
Account string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateNewAccountCmd returns a new instance which can be used to issue a
|
||||||
|
// createnewaccount JSON-RPC command.
|
||||||
|
func NewCreateNewAccountCmd(account string) *CreateNewAccountCmd {
|
||||||
|
return &CreateNewAccountCmd{
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpWalletCmd defines the dumpwallet JSON-RPC command.
|
||||||
|
type DumpWalletCmd struct {
|
||||||
|
Filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDumpWalletCmd returns a new instance which can be used to issue a
|
||||||
|
// dumpwallet JSON-RPC command.
|
||||||
|
func NewDumpWalletCmd(filename string) *DumpWalletCmd {
|
||||||
|
return &DumpWalletCmd{
|
||||||
|
Filename: filename,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportAddressCmd defines the importaddress JSON-RPC command.
|
||||||
|
type ImportAddressCmd struct {
|
||||||
|
Address string
|
||||||
|
Rescan *bool `jsonrpcdefault:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImportAddressCmd returns a new instance which can be used to issue an
|
||||||
|
// importaddress JSON-RPC command.
|
||||||
|
func NewImportAddressCmd(address string, rescan *bool) *ImportAddressCmd {
|
||||||
|
return &ImportAddressCmd{
|
||||||
|
Address: address,
|
||||||
|
Rescan: rescan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPubKeyCmd defines the importpubkey JSON-RPC command.
|
||||||
|
type ImportPubKeyCmd struct {
|
||||||
|
PubKey string
|
||||||
|
Rescan *bool `jsonrpcdefault:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImportPubKeyCmd returns a new instance which can be used to issue an
|
||||||
|
// importpubkey JSON-RPC command.
|
||||||
|
func NewImportPubKeyCmd(pubKey string, rescan *bool) *ImportPubKeyCmd {
|
||||||
|
return &ImportPubKeyCmd{
|
||||||
|
PubKey: pubKey,
|
||||||
|
Rescan: rescan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportWalletCmd defines the importwallet JSON-RPC command.
|
||||||
|
type ImportWalletCmd struct {
|
||||||
|
Filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImportWalletCmd returns a new instance which can be used to issue a
|
||||||
|
// importwallet JSON-RPC command.
|
||||||
|
func NewImportWalletCmd(filename string) *ImportWalletCmd {
|
||||||
|
return &ImportWalletCmd{
|
||||||
|
Filename: filename,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenameAccountCmd defines the renameaccount JSON-RPC command.
|
||||||
|
type RenameAccountCmd struct {
|
||||||
|
OldAccount string
|
||||||
|
NewAccount string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRenameAccountCmd returns a new instance which can be used to issue a
|
||||||
|
// renameaccount JSON-RPC command.
|
||||||
|
func NewRenameAccountCmd(oldAccount, newAccount string) *RenameAccountCmd {
|
||||||
|
return &RenameAccountCmd{
|
||||||
|
OldAccount: oldAccount,
|
||||||
|
NewAccount: newAccount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// The commands in this file are only usable with a wallet server.
|
||||||
|
flags := UFWalletOnly
|
||||||
|
|
||||||
|
MustRegisterCmd("createnewaccount", (*CreateNewAccountCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("dumpwallet", (*DumpWalletCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("importaddress", (*ImportAddressCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("importpubkey", (*ImportPubKeyCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("importwallet", (*ImportWalletCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("renameaccount", (*RenameAccountCmd)(nil), flags)
|
||||||
|
}
|
208
btcjson/v2/btcjson/btcwalletextcmds_test.go
Normal file
208
btcjson/v2/btcjson/btcwalletextcmds_test.go
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBtcWalletExtCmds tests all of the btcwallet extended commands marshal and
|
||||||
|
// unmarshal into valid results include handling of optional fields being
|
||||||
|
// omitted in the marshalled command, while optional fields with defaults have
|
||||||
|
// the default assigned on unmarshalled commands.
|
||||||
|
func TestBtcWalletExtCmds(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testID := int(1)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
newCmd func() (interface{}, error)
|
||||||
|
staticCmd func() interface{}
|
||||||
|
marshalled string
|
||||||
|
unmarshalled interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "createnewaccount",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("createnewaccount", "acct")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewCreateNewAccountCmd("acct")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"createnewaccount","params":["acct"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.CreateNewAccountCmd{
|
||||||
|
Account: "acct",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dumpwallet",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("dumpwallet", "filename")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewDumpWalletCmd("filename")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"dumpwallet","params":["filename"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.DumpWalletCmd{
|
||||||
|
Filename: "filename",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "importaddress",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("importaddress", "1Address")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewImportAddressCmd("1Address", nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"importaddress","params":["1Address"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ImportAddressCmd{
|
||||||
|
Address: "1Address",
|
||||||
|
Rescan: btcjson.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "importaddress optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("importaddress", "1Address", false)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewImportAddressCmd("1Address", btcjson.Bool(false))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"importaddress","params":["1Address",false],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ImportAddressCmd{
|
||||||
|
Address: "1Address",
|
||||||
|
Rescan: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "importpubkey",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("importpubkey", "031234")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewImportPubKeyCmd("031234", nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"importpubkey","params":["031234"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ImportPubKeyCmd{
|
||||||
|
PubKey: "031234",
|
||||||
|
Rescan: btcjson.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "importpubkey optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("importpubkey", "031234", false)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewImportPubKeyCmd("031234", btcjson.Bool(false))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"importpubkey","params":["031234",false],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ImportPubKeyCmd{
|
||||||
|
PubKey: "031234",
|
||||||
|
Rescan: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "importwallet",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("importwallet", "filename")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewImportWalletCmd("filename")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"importwallet","params":["filename"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ImportWalletCmd{
|
||||||
|
Filename: "filename",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "renameaccount",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("renameaccount", "oldacct", "newacct")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewRenameAccountCmd("oldacct", "newacct")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"renameaccount","params":["oldacct","newacct"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.RenameAccountCmd{
|
||||||
|
OldAccount: "oldacct",
|
||||||
|
NewAccount: "newacct",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Marshal the command as created by the new static command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is created without error via the generic
|
||||||
|
// new command creation function.
|
||||||
|
cmd, err := test.newCmd()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||||
|
i, test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the command as created by the generic new command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var request btcjson.Request
|
||||||
|
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||||
|
"unmarshalling JSON-RPC request: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||||
|
"- got %s, want %s", i, test.name,
|
||||||
|
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||||
|
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
697
btcjson/v2/btcjson/chainsvrcmds.go
Normal file
697
btcjson/v2/btcjson/chainsvrcmds.go
Normal file
|
@ -0,0 +1,697 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||||
|
// a chain server.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddNodeSubCmd defines the type used in the addnode JSON-RPC command for the
|
||||||
|
// sub command field.
|
||||||
|
type AddNodeSubCmd string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ANAdd indicates the specified host should be added as a persistent
|
||||||
|
// peer.
|
||||||
|
ANAdd AddNodeSubCmd = "add"
|
||||||
|
|
||||||
|
// ANRemove indicates the specified peer should be removed.
|
||||||
|
ANRemove AddNodeSubCmd = "remove"
|
||||||
|
|
||||||
|
// ANOneTry indicates the specified host should try to connect once,
|
||||||
|
// but it should not be made persistent.
|
||||||
|
ANOneTry AddNodeSubCmd = "onetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddNodeCmd defines the addnode JSON-RPC command.
|
||||||
|
type AddNodeCmd struct {
|
||||||
|
Addr string
|
||||||
|
SubCmd AddNodeSubCmd `jsonrpcusage:"\"add|remove|onetry\""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAddNodeCmd returns a new instance which can be used to issue an addnode
|
||||||
|
// JSON-RPC command.
|
||||||
|
func NewAddNodeCmd(addr string, subCmd AddNodeSubCmd) *AddNodeCmd {
|
||||||
|
return &AddNodeCmd{
|
||||||
|
Addr: addr,
|
||||||
|
SubCmd: subCmd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionInput represents the inputs to a transaction. Specifically a
|
||||||
|
// transaction hash and output number pair.
|
||||||
|
type TransactionInput struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command.
|
||||||
|
type CreateRawTransactionCmd struct {
|
||||||
|
Inputs []TransactionInput
|
||||||
|
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateRawTransactionCmd returns a new instance which can be used to issue
|
||||||
|
// a createrawtransaction JSON-RPC command.
|
||||||
|
//
|
||||||
|
// Amounts are in BTC.
|
||||||
|
func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]float64) *CreateRawTransactionCmd {
|
||||||
|
return &CreateRawTransactionCmd{
|
||||||
|
Inputs: inputs,
|
||||||
|
Amounts: amounts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeRawTransactionCmd defines the decoderawtransaction JSON-RPC command.
|
||||||
|
type DecodeRawTransactionCmd struct {
|
||||||
|
HexTx string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecodeRawTransactionCmd returns a new instance which can be used to issue
|
||||||
|
// a decoderawtransaction JSON-RPC command.
|
||||||
|
func NewDecodeRawTransactionCmd(hexTx string) *DecodeRawTransactionCmd {
|
||||||
|
return &DecodeRawTransactionCmd{
|
||||||
|
HexTx: hexTx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeScriptCmd defines the decodescript JSON-RPC command.
|
||||||
|
type DecodeScriptCmd struct {
|
||||||
|
HexScript string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecodeScriptCmd returns a new instance which can be used to issue a
|
||||||
|
// decodescript JSON-RPC command.
|
||||||
|
func NewDecodeScriptCmd(hexScript string) *DecodeScriptCmd {
|
||||||
|
return &DecodeScriptCmd{
|
||||||
|
HexScript: hexScript,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddedNodeInfoCmd defines the getaddednodeinfo JSON-RPC command.
|
||||||
|
type GetAddedNodeInfoCmd struct {
|
||||||
|
DNS bool
|
||||||
|
Node *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetAddedNodeInfoCmd returns a new instance which can be used to issue a
|
||||||
|
// getaddednodeinfo JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetAddedNodeInfoCmd(dns bool, node *string) *GetAddedNodeInfoCmd {
|
||||||
|
return &GetAddedNodeInfoCmd{
|
||||||
|
DNS: dns,
|
||||||
|
Node: node,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBestBlockHashCmd defines the getbestblockhash JSON-RPC command.
|
||||||
|
type GetBestBlockHashCmd struct{}
|
||||||
|
|
||||||
|
// NewGetBestBlockHashCmd returns a new instance which can be used to issue a
|
||||||
|
// getbestblockhash JSON-RPC command.
|
||||||
|
func NewGetBestBlockHashCmd() *GetBestBlockHashCmd {
|
||||||
|
return &GetBestBlockHashCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockCmd defines the getblock JSON-RPC command.
|
||||||
|
type GetBlockCmd struct {
|
||||||
|
Hash string
|
||||||
|
Verbose *bool `jsonrpcdefault:"true"`
|
||||||
|
VerboseTx *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetBlockCmd returns a new instance which can be used to issue a getblock
|
||||||
|
// JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetBlockCmd(hash string, verbose, verboseTx *bool) *GetBlockCmd {
|
||||||
|
return &GetBlockCmd{
|
||||||
|
Hash: hash,
|
||||||
|
Verbose: verbose,
|
||||||
|
VerboseTx: verboseTx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockChainInfoCmd defines the getblockchaininfo JSON-RPC command.
|
||||||
|
type GetBlockChainInfoCmd struct{}
|
||||||
|
|
||||||
|
// NewGetBlockChainInfoCmd returns a new instance which can be used to issue a
|
||||||
|
// getblockchaininfo JSON-RPC command.
|
||||||
|
func NewGetBlockChainInfoCmd() *GetBlockChainInfoCmd {
|
||||||
|
return &GetBlockChainInfoCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockCountCmd defines the getblockcount JSON-RPC command.
|
||||||
|
type GetBlockCountCmd struct{}
|
||||||
|
|
||||||
|
// NewGetBlockCountCmd returns a new instance which can be used to issue a
|
||||||
|
// getblockcount JSON-RPC command.
|
||||||
|
func NewGetBlockCountCmd() *GetBlockCountCmd {
|
||||||
|
return &GetBlockCountCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockHashCmd defines the getblockhash JSON-RPC command.
|
||||||
|
type GetBlockHashCmd struct {
|
||||||
|
Index int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetBlockHashCmd returns a new instance which can be used to issue a
|
||||||
|
// getblockhash JSON-RPC command.
|
||||||
|
func NewGetBlockHashCmd(index int64) *GetBlockHashCmd {
|
||||||
|
return &GetBlockHashCmd{
|
||||||
|
Index: index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateRequest is a request object as defined in BIP22
|
||||||
|
// (https://en.bitcoin.it/wiki/BIP_0022), it is optionally provided as an
|
||||||
|
// pointer argument to GetBlockTemplateCmd.
|
||||||
|
type TemplateRequest struct {
|
||||||
|
Mode string `json:"mode,omitempty"`
|
||||||
|
Capabilities []string `json:"capabilities,omitempty"`
|
||||||
|
|
||||||
|
// Optional long polling.
|
||||||
|
LongPollID string `json:"longpollid,omitempty"`
|
||||||
|
|
||||||
|
// Optional template tweaking. SigOpLimit and SizeLimit can be int64
|
||||||
|
// or bool.
|
||||||
|
SigOpLimit interface{} `json:"sigoplimit,omitempty"`
|
||||||
|
SizeLimit interface{} `json:"sizelimit,omitempty"`
|
||||||
|
MaxVersion uint32 `json:"maxversion,omitempty"`
|
||||||
|
|
||||||
|
// Basic pool extension from BIP 0023.
|
||||||
|
Target string `json:"target,omitempty"`
|
||||||
|
|
||||||
|
// Block proposal from BIP 0023. Data is only provided when Mode is
|
||||||
|
// "proposal".
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
WorkID string `json:"workid,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertTemplateRequestField potentially converts the provided value as
|
||||||
|
// needed.
|
||||||
|
func convertTemplateRequestField(fieldName string, iface interface{}) (interface{}, error) {
|
||||||
|
switch val := iface.(type) {
|
||||||
|
case nil:
|
||||||
|
return nil, nil
|
||||||
|
case bool:
|
||||||
|
return val, nil
|
||||||
|
case float64:
|
||||||
|
if val == float64(int64(val)) {
|
||||||
|
return int64(val), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str := fmt.Sprintf("the %s field must be unspecified, a boolean, or "+
|
||||||
|
"a 64-bit integer", fieldName)
|
||||||
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON provides a custom Unmarshal method for TemplateRequest. This
|
||||||
|
// is necessary because the SigOpLimit and SizeLimit fields can only be specific
|
||||||
|
// types.
|
||||||
|
func (t *TemplateRequest) UnmarshalJSON(data []byte) error {
|
||||||
|
type templateRequest TemplateRequest
|
||||||
|
|
||||||
|
request := (*templateRequest)(t)
|
||||||
|
if err := json.Unmarshal(data, &request); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The SigOpLimit field can only be nil, bool, or int64.
|
||||||
|
val, err := convertTemplateRequestField("sigoplimit", request.SigOpLimit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.SigOpLimit = val
|
||||||
|
|
||||||
|
// The SizeLimit field can only be nil, bool, or int64.
|
||||||
|
val, err = convertTemplateRequestField("sizelimit", request.SizeLimit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.SizeLimit = val
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTemplateCmd defines the getblocktemplate JSON-RPC command.
|
||||||
|
type GetBlockTemplateCmd struct {
|
||||||
|
Request *TemplateRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetBlockTemplateCmd returns a new instance which can be used to issue a
|
||||||
|
// getblocktemplate JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetBlockTemplateCmd(request *TemplateRequest) *GetBlockTemplateCmd {
|
||||||
|
return &GetBlockTemplateCmd{
|
||||||
|
Request: request,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChainTipsCmd defines the getchaintips JSON-RPC command.
|
||||||
|
type GetChainTipsCmd struct{}
|
||||||
|
|
||||||
|
// NewGetChainTipsCmd returns a new instance which can be used to issue a
|
||||||
|
// getchaintips JSON-RPC command.
|
||||||
|
func NewGetChainTipsCmd() *GetChainTipsCmd {
|
||||||
|
return &GetChainTipsCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConnectionCountCmd defines the getconnectioncount JSON-RPC command.
|
||||||
|
type GetConnectionCountCmd struct{}
|
||||||
|
|
||||||
|
// NewGetConnectionCountCmd returns a new instance which can be used to issue a
|
||||||
|
// getconnectioncount JSON-RPC command.
|
||||||
|
func NewGetConnectionCountCmd() *GetConnectionCountCmd {
|
||||||
|
return &GetConnectionCountCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDifficultyCmd defines the getdifficulty JSON-RPC command.
|
||||||
|
type GetDifficultyCmd struct{}
|
||||||
|
|
||||||
|
// NewGetDifficultyCmd returns a new instance which can be used to issue a
|
||||||
|
// getdifficulty JSON-RPC command.
|
||||||
|
func NewGetDifficultyCmd() *GetDifficultyCmd {
|
||||||
|
return &GetDifficultyCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGenerateCmd defines the getgenerate JSON-RPC command.
|
||||||
|
type GetGenerateCmd struct{}
|
||||||
|
|
||||||
|
// NewGetGenerateCmd returns a new instance which can be used to issue a
|
||||||
|
// getgenerate JSON-RPC command.
|
||||||
|
func NewGetGenerateCmd() *GetGenerateCmd {
|
||||||
|
return &GetGenerateCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHashesPerSecCmd defines the gethashespersec JSON-RPC command.
|
||||||
|
type GetHashesPerSecCmd struct{}
|
||||||
|
|
||||||
|
// NewGetHashesPerSecCmd returns a new instance which can be used to issue a
|
||||||
|
// gethashespersec JSON-RPC command.
|
||||||
|
func NewGetHashesPerSecCmd() *GetHashesPerSecCmd {
|
||||||
|
return &GetHashesPerSecCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInfoCmd defines the getinfo JSON-RPC command.
|
||||||
|
type GetInfoCmd struct{}
|
||||||
|
|
||||||
|
// NewGetInfoCmd returns a new instance which can be used to issue a
|
||||||
|
// getinfo JSON-RPC command.
|
||||||
|
func NewGetInfoCmd() *GetInfoCmd {
|
||||||
|
return &GetInfoCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMempoolInfoCmd defines the getmempoolinfo JSON-RPC command.
|
||||||
|
type GetMempoolInfoCmd struct{}
|
||||||
|
|
||||||
|
// NewGetMempoolInfoCmd returns a new instance which can be used to issue a
|
||||||
|
// getmempool JSON-RPC command.
|
||||||
|
func NewGetMempoolInfoCmd() *GetMempoolInfoCmd {
|
||||||
|
return &GetMempoolInfoCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMiningInfoCmd defines the getmininginfo JSON-RPC command.
|
||||||
|
type GetMiningInfoCmd struct{}
|
||||||
|
|
||||||
|
// NewGetMiningInfoCmd returns a new instance which can be used to issue a
|
||||||
|
// getmininginfo JSON-RPC command.
|
||||||
|
func NewGetMiningInfoCmd() *GetMiningInfoCmd {
|
||||||
|
return &GetMiningInfoCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkInfoCmd defines the getnetworkinfo JSON-RPC command.
|
||||||
|
type GetNetworkInfoCmd struct{}
|
||||||
|
|
||||||
|
// NewGetNetworkInfoCmd returns a new instance which can be used to issue a
|
||||||
|
// getnetworkinfo JSON-RPC command.
|
||||||
|
func NewGetNetworkInfoCmd() *GetNetworkInfoCmd {
|
||||||
|
return &GetNetworkInfoCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetTotalsCmd defines the getnettotals JSON-RPC command.
|
||||||
|
type GetNetTotalsCmd struct{}
|
||||||
|
|
||||||
|
// NewGetNetTotalsCmd returns a new instance which can be used to issue a
|
||||||
|
// getnettotals JSON-RPC command.
|
||||||
|
func NewGetNetTotalsCmd() *GetNetTotalsCmd {
|
||||||
|
return &GetNetTotalsCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkHashPSCmd defines the getnetworkhashps JSON-RPC command.
|
||||||
|
type GetNetworkHashPSCmd struct {
|
||||||
|
Blocks *int `jsonrpcdefault:"120"`
|
||||||
|
Height *int `jsonrpcdefault:"-1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetNetworkHashPSCmd returns a new instance which can be used to issue a
|
||||||
|
// getnetworkhashps JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetNetworkHashPSCmd(numBlocks, height *int) *GetNetworkHashPSCmd {
|
||||||
|
return &GetNetworkHashPSCmd{
|
||||||
|
Blocks: numBlocks,
|
||||||
|
Height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeerInfoCmd defines the getpeerinfo JSON-RPC command.
|
||||||
|
type GetPeerInfoCmd struct{}
|
||||||
|
|
||||||
|
// NewGetPeerInfoCmd returns a new instance which can be used to issue a getpeer
|
||||||
|
// JSON-RPC command.
|
||||||
|
func NewGetPeerInfoCmd() *GetPeerInfoCmd {
|
||||||
|
return &GetPeerInfoCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawMempoolCmd defines the getmempool JSON-RPC command.
|
||||||
|
type GetRawMempoolCmd struct {
|
||||||
|
Verbose *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetRawMempoolCmd returns a new instance which can be used to issue a
|
||||||
|
// getrawmempool JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetRawMempoolCmd(verbose *bool) *GetRawMempoolCmd {
|
||||||
|
return &GetRawMempoolCmd{
|
||||||
|
Verbose: verbose,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawTransactionCmd defines the getrawtransaction JSON-RPC command.
|
||||||
|
//
|
||||||
|
// NOTE: This field is an int versus a bool to remain compatible with Bitcoin
|
||||||
|
// Core even though it really should be a bool.
|
||||||
|
type GetRawTransactionCmd struct {
|
||||||
|
Txid string
|
||||||
|
Verbose *int `jsonrpcdefault:"0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetRawTransactionCmd returns a new instance which can be used to issue a
|
||||||
|
// getrawtransaction JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetRawTransactionCmd(txHash string, verbose *int) *GetRawTransactionCmd {
|
||||||
|
return &GetRawTransactionCmd{
|
||||||
|
Txid: txHash,
|
||||||
|
Verbose: verbose,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxOutCmd defines the gettxout JSON-RPC command.
|
||||||
|
type GetTxOutCmd struct {
|
||||||
|
Txid string
|
||||||
|
Vout int
|
||||||
|
IncludeMempool *bool `jsonrpcdefault:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetTxOutCmd returns a new instance which can be used to issue a gettxout
|
||||||
|
// JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetTxOutCmd(txHash string, vout int, includeMempool *bool) *GetTxOutCmd {
|
||||||
|
return &GetTxOutCmd{
|
||||||
|
Txid: txHash,
|
||||||
|
Vout: vout,
|
||||||
|
IncludeMempool: includeMempool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxOutSetInfoCmd defines the gettxoutsetinfo JSON-RPC command.
|
||||||
|
type GetTxOutSetInfoCmd struct{}
|
||||||
|
|
||||||
|
// NewGetTxOutSetInfoCmd returns a new instance which can be used to issue a
|
||||||
|
// gettxoutsetinfo JSON-RPC command.
|
||||||
|
func NewGetTxOutSetInfoCmd() *GetTxOutSetInfoCmd {
|
||||||
|
return &GetTxOutSetInfoCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWorkCmd defines the getwork JSON-RPC command.
|
||||||
|
type GetWorkCmd struct {
|
||||||
|
Data *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetWorkCmd returns a new instance which can be used to issue a getwork
|
||||||
|
// JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetWorkCmd(data *string) *GetWorkCmd {
|
||||||
|
return &GetWorkCmd{
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelpCmd defines the help JSON-RPC command.
|
||||||
|
type HelpCmd struct {
|
||||||
|
Command *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHelpCmd returns a new instance which can be used to issue a help JSON-RPC
|
||||||
|
// command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewHelpCmd(command *string) *HelpCmd {
|
||||||
|
return &HelpCmd{
|
||||||
|
Command: command,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidateBlockCmd defines the invalidateblock JSON-RPC command.
|
||||||
|
type InvalidateBlockCmd struct {
|
||||||
|
BlockHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInvalidateBlockCmd returns a new instance which can be used to issue a
|
||||||
|
// invalidateblock JSON-RPC command.
|
||||||
|
func NewInvalidateBlockCmd(blockHash string) *InvalidateBlockCmd {
|
||||||
|
return &InvalidateBlockCmd{
|
||||||
|
BlockHash: blockHash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PingCmd defines the ping JSON-RPC command.
|
||||||
|
type PingCmd struct{}
|
||||||
|
|
||||||
|
// NewPingCmd returns a new instance which can be used to issue a ping JSON-RPC
|
||||||
|
// command.
|
||||||
|
func NewPingCmd() *PingCmd {
|
||||||
|
return &PingCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReconsiderBlockCmd defines the reconsiderblock JSON-RPC command.
|
||||||
|
type ReconsiderBlockCmd struct {
|
||||||
|
BlockHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReconsiderBlockCmd returns a new instance which can be used to issue a
|
||||||
|
// reconsiderblock JSON-RPC command.
|
||||||
|
func NewReconsiderBlockCmd(blockHash string) *ReconsiderBlockCmd {
|
||||||
|
return &ReconsiderBlockCmd{
|
||||||
|
BlockHash: blockHash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchRawTransactionsCmd defines the searchrawtransactions JSON-RPC command.
|
||||||
|
type SearchRawTransactionsCmd struct {
|
||||||
|
Address string
|
||||||
|
Verbose *bool `jsonrpcdefault:"true"`
|
||||||
|
Skip *int `jsonrpcdefault:"0"`
|
||||||
|
Count *int `jsonrpcdefault:"100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSearchRawTransactionsCmd returns a new instance which can be used to issue a
|
||||||
|
// sendrawtransaction JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewSearchRawTransactionsCmd(address string, verbose *bool, skip, count *int) *SearchRawTransactionsCmd {
|
||||||
|
return &SearchRawTransactionsCmd{
|
||||||
|
Address: address,
|
||||||
|
Verbose: verbose,
|
||||||
|
Skip: skip,
|
||||||
|
Count: count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendRawTransactionCmd defines the sendrawtransaction JSON-RPC command.
|
||||||
|
type SendRawTransactionCmd struct {
|
||||||
|
HexTx string
|
||||||
|
AllowHighFees *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSendRawTransactionCmd returns a new instance which can be used to issue a
|
||||||
|
// sendrawtransaction JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewSendRawTransactionCmd(hexTx string, allowHighFees *bool) *SendRawTransactionCmd {
|
||||||
|
return &SendRawTransactionCmd{
|
||||||
|
HexTx: hexTx,
|
||||||
|
AllowHighFees: allowHighFees,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGenerateCmd defines the setgenerate JSON-RPC command.
|
||||||
|
type SetGenerateCmd struct {
|
||||||
|
Generate bool
|
||||||
|
GenProcLimit *int `jsonrpcdefault:"-1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSetGenerateCmd returns a new instance which can be used to issue a
|
||||||
|
// setgenerate JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewSetGenerateCmd(generate bool, genProcLimit *int) *SetGenerateCmd {
|
||||||
|
return &SetGenerateCmd{
|
||||||
|
Generate: generate,
|
||||||
|
GenProcLimit: genProcLimit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopCmd defines the stop JSON-RPC command.
|
||||||
|
type StopCmd struct{}
|
||||||
|
|
||||||
|
// NewStopCmd returns a new instance which can be used to issue a stop JSON-RPC
|
||||||
|
// command.
|
||||||
|
func NewStopCmd() *StopCmd {
|
||||||
|
return &StopCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitBlockOptions represents the optional options struct provided with a
|
||||||
|
// SubmitBlockCmd command.
|
||||||
|
type SubmitBlockOptions struct {
|
||||||
|
// must be provided if server provided a workid with template.
|
||||||
|
WorkID string `json:"workid,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitBlockCmd defines the submitblock JSON-RPC command.
|
||||||
|
type SubmitBlockCmd struct {
|
||||||
|
HexBlock string
|
||||||
|
Options *SubmitBlockOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubmitBlockCmd returns a new instance which can be used to issue a
|
||||||
|
// submitblock JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewSubmitBlockCmd(hexBlock string, options *SubmitBlockOptions) *SubmitBlockCmd {
|
||||||
|
return &SubmitBlockCmd{
|
||||||
|
HexBlock: hexBlock,
|
||||||
|
Options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAddressCmd defines the validateaddress JSON-RPC command.
|
||||||
|
type ValidateAddressCmd struct {
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewValidateAddressCmd returns a new instance which can be used to issue a
|
||||||
|
// validateaddress JSON-RPC command.
|
||||||
|
func NewValidateAddressCmd(address string) *ValidateAddressCmd {
|
||||||
|
return &ValidateAddressCmd{
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyChainCmd defines the verifychain JSON-RPC command.
|
||||||
|
type VerifyChainCmd struct {
|
||||||
|
CheckLevel *int32 `jsonrpcdefault:"3"`
|
||||||
|
CheckDepth *int32 `jsonrpcdefault:"288"` // 0 = all
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVerifyChainCmd returns a new instance which can be used to issue a
|
||||||
|
// verifychain JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewVerifyChainCmd(checkLevel, checkDepth *int32) *VerifyChainCmd {
|
||||||
|
return &VerifyChainCmd{
|
||||||
|
CheckLevel: checkLevel,
|
||||||
|
CheckDepth: checkDepth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyMessageCmd defines the verifymessage JSON-RPC command.
|
||||||
|
type VerifyMessageCmd struct {
|
||||||
|
Address string
|
||||||
|
Signature string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVerifyMessageCmd returns a new instance which can be used to issue a
|
||||||
|
// verifymessage JSON-RPC command.
|
||||||
|
func NewVerifyMessageCmd(address, signature, message string) *VerifyMessageCmd {
|
||||||
|
return &VerifyMessageCmd{
|
||||||
|
Address: address,
|
||||||
|
Signature: signature,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// No special flags for commands in this file.
|
||||||
|
flags := UsageFlag(0)
|
||||||
|
|
||||||
|
MustRegisterCmd("addnode", (*AddNodeCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("createrawtransaction", (*CreateRawTransactionCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("decoderawtransaction", (*DecodeRawTransactionCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("decodescript", (*DecodeScriptCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getaddednodeinfo", (*GetAddedNodeInfoCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getbestblockhash", (*GetBestBlockHashCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getblock", (*GetBlockCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getblockchaininfo", (*GetBlockChainInfoCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getblockcount", (*GetBlockCountCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getblockhash", (*GetBlockHashCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getblocktemplate", (*GetBlockTemplateCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getchaintips", (*GetChainTipsCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getconnectioncount", (*GetConnectionCountCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getdifficulty", (*GetDifficultyCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getgenerate", (*GetGenerateCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("gethashespersec", (*GetHashesPerSecCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getinfo", (*GetInfoCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getmempoolinfo", (*GetMempoolInfoCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getmininginfo", (*GetMiningInfoCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getnetworkinfo", (*GetNetworkInfoCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getnettotals", (*GetNetTotalsCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getnetworkhashps", (*GetNetworkHashPSCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getpeerinfo", (*GetPeerInfoCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getrawmempool", (*GetRawMempoolCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getrawtransaction", (*GetRawTransactionCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("gettxout", (*GetTxOutCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("gettxoutsetinfo", (*GetTxOutSetInfoCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getwork", (*GetWorkCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("help", (*HelpCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("invalidateblock", (*InvalidateBlockCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("ping", (*PingCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("reconsiderblock", (*ReconsiderBlockCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("searchrawtransactions", (*SearchRawTransactionsCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("sendrawtransaction", (*SendRawTransactionCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("setgenerate", (*SetGenerateCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("stop", (*StopCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("submitblock", (*SubmitBlockCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("validateaddress", (*ValidateAddressCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("verifychain", (*VerifyChainCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("verifymessage", (*VerifyMessageCmd)(nil), flags)
|
||||||
|
}
|
988
btcjson/v2/btcjson/chainsvrcmds_test.go
Normal file
988
btcjson/v2/btcjson/chainsvrcmds_test.go
Normal file
|
@ -0,0 +1,988 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestChainSvrCmds tests all of the chain server commands marshal and unmarshal
|
||||||
|
// into valid results include handling of optional fields being omitted in the
|
||||||
|
// marshalled command, while optional fields with defaults have the default
|
||||||
|
// assigned on unmarshalled commands.
|
||||||
|
func TestChainSvrCmds(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testID := int(1)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
newCmd func() (interface{}, error)
|
||||||
|
staticCmd func() interface{}
|
||||||
|
marshalled string
|
||||||
|
unmarshalled interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "addnode",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("addnode", "127.0.0.1", btcjson.ANRemove)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewAddNodeCmd("127.0.0.1", btcjson.ANRemove)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"addnode","params":["127.0.0.1","remove"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.AddNodeCmd{Addr: "127.0.0.1", SubCmd: btcjson.ANRemove},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "createrawtransaction",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("createrawtransaction", `[{"txid":"123","vout":1}]`,
|
||||||
|
`{"456":0.0123}`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
txInputs := []btcjson.TransactionInput{
|
||||||
|
{Txid: "123", Vout: 1},
|
||||||
|
}
|
||||||
|
amounts := map[string]float64{"456": .0123}
|
||||||
|
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||||
|
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||||
|
Amounts: map[string]float64{"456": .0123},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "decoderawtransaction",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("decoderawtransaction", "123")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewDecodeRawTransactionCmd("123")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"decoderawtransaction","params":["123"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.DecodeRawTransactionCmd{HexTx: "123"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "decodescript",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("decodescript", "00")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewDecodeScriptCmd("00")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"decodescript","params":["00"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.DecodeScriptCmd{HexScript: "00"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getaddednodeinfo",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getaddednodeinfo", true)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetAddedNodeInfoCmd(true, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getaddednodeinfo","params":[true],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetAddedNodeInfoCmd{DNS: true, Node: nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getaddednodeinfo optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getaddednodeinfo", true, "127.0.0.1")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetAddedNodeInfoCmd(true, btcjson.String("127.0.0.1"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getaddednodeinfo","params":[true,"127.0.0.1"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetAddedNodeInfoCmd{
|
||||||
|
DNS: true,
|
||||||
|
Node: btcjson.String("127.0.0.1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getbestblockhash",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getbestblockhash")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetBestBlockHashCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getbestblockhash","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBestBlockHashCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblock",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getblock", "123")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetBlockCmd("123", nil, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockCmd{
|
||||||
|
Hash: "123",
|
||||||
|
Verbose: btcjson.Bool(true),
|
||||||
|
VerboseTx: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblock required optional1",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
// Intentionally use a source param that is
|
||||||
|
// more pointers than the destination to
|
||||||
|
// exercise that path.
|
||||||
|
verbosePtr := btcjson.Bool(true)
|
||||||
|
return btcjson.NewCmd("getblock", "123", &verbosePtr)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetBlockCmd("123", btcjson.Bool(true), nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",true],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockCmd{
|
||||||
|
Hash: "123",
|
||||||
|
Verbose: btcjson.Bool(true),
|
||||||
|
VerboseTx: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblock required optional2",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getblock", "123", true, true)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetBlockCmd("123", btcjson.Bool(true), btcjson.Bool(true))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",true,true],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockCmd{
|
||||||
|
Hash: "123",
|
||||||
|
Verbose: btcjson.Bool(true),
|
||||||
|
VerboseTx: btcjson.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblockchaininfo",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getblockchaininfo")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetBlockChainInfoCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblockchaininfo","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockChainInfoCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblockcount",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getblockcount")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetBlockCountCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblockcount","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockCountCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblockhash",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getblockhash", 123)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetBlockHashCmd(123)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblockhash","params":[123],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockHashCmd{Index: 123},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblocktemplate",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getblocktemplate")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetBlockTemplateCmd(nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblocktemplate","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockTemplateCmd{Request: nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblocktemplate optional - template request",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getblocktemplate", `{"mode":"template","capabilities":["longpoll","coinbasetxn"]}`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
template := btcjson.TemplateRequest{
|
||||||
|
Mode: "template",
|
||||||
|
Capabilities: []string{"longpoll", "coinbasetxn"},
|
||||||
|
}
|
||||||
|
return btcjson.NewGetBlockTemplateCmd(&template)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblocktemplate","params":[{"mode":"template","capabilities":["longpoll","coinbasetxn"]}],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockTemplateCmd{
|
||||||
|
Request: &btcjson.TemplateRequest{
|
||||||
|
Mode: "template",
|
||||||
|
Capabilities: []string{"longpoll", "coinbasetxn"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblocktemplate optional - template request with tweaks",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getblocktemplate", `{"mode":"template","capabilities":["longpoll","coinbasetxn"],"sigoplimit":500,"sizelimit":100000000,"maxversion":2}`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
template := btcjson.TemplateRequest{
|
||||||
|
Mode: "template",
|
||||||
|
Capabilities: []string{"longpoll", "coinbasetxn"},
|
||||||
|
SigOpLimit: 500,
|
||||||
|
SizeLimit: 100000000,
|
||||||
|
MaxVersion: 2,
|
||||||
|
}
|
||||||
|
return btcjson.NewGetBlockTemplateCmd(&template)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblocktemplate","params":[{"mode":"template","capabilities":["longpoll","coinbasetxn"],"sigoplimit":500,"sizelimit":100000000,"maxversion":2}],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockTemplateCmd{
|
||||||
|
Request: &btcjson.TemplateRequest{
|
||||||
|
Mode: "template",
|
||||||
|
Capabilities: []string{"longpoll", "coinbasetxn"},
|
||||||
|
SigOpLimit: int64(500),
|
||||||
|
SizeLimit: int64(100000000),
|
||||||
|
MaxVersion: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblocktemplate optional - template request with tweaks 2",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getblocktemplate", `{"mode":"template","capabilities":["longpoll","coinbasetxn"],"sigoplimit":true,"sizelimit":100000000,"maxversion":2}`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
template := btcjson.TemplateRequest{
|
||||||
|
Mode: "template",
|
||||||
|
Capabilities: []string{"longpoll", "coinbasetxn"},
|
||||||
|
SigOpLimit: true,
|
||||||
|
SizeLimit: 100000000,
|
||||||
|
MaxVersion: 2,
|
||||||
|
}
|
||||||
|
return btcjson.NewGetBlockTemplateCmd(&template)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getblocktemplate","params":[{"mode":"template","capabilities":["longpoll","coinbasetxn"],"sigoplimit":true,"sizelimit":100000000,"maxversion":2}],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetBlockTemplateCmd{
|
||||||
|
Request: &btcjson.TemplateRequest{
|
||||||
|
Mode: "template",
|
||||||
|
Capabilities: []string{"longpoll", "coinbasetxn"},
|
||||||
|
SigOpLimit: true,
|
||||||
|
SizeLimit: int64(100000000),
|
||||||
|
MaxVersion: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getchaintips",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getchaintips")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetChainTipsCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getchaintips","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetChainTipsCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getconnectioncount",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getconnectioncount")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetConnectionCountCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getconnectioncount","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetConnectionCountCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getdifficulty",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getdifficulty")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetDifficultyCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getdifficulty","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetDifficultyCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getgenerate",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getgenerate")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetGenerateCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getgenerate","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetGenerateCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gethashespersec",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("gethashespersec")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetHashesPerSecCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"gethashespersec","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetHashesPerSecCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getinfo",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getinfo")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetInfoCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getinfo","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetInfoCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getmempoolinfo",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getmempoolinfo")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetMempoolInfoCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getmempoolinfo","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetMempoolInfoCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getmininginfo",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getmininginfo")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetMiningInfoCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getmininginfo","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetMiningInfoCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getnetworkinfo",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getnetworkinfo")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetNetworkInfoCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getnetworkinfo","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetNetworkInfoCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getnettotals",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getnettotals")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetNetTotalsCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getnettotals","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetNetTotalsCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getnetworkhashps",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getnetworkhashps")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetNetworkHashPSCmd(nil, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetNetworkHashPSCmd{
|
||||||
|
Blocks: btcjson.Int(120),
|
||||||
|
Height: btcjson.Int(-1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getnetworkhashps optional1",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getnetworkhashps", 200)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetNetworkHashPSCmd(btcjson.Int(200), nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[200],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetNetworkHashPSCmd{
|
||||||
|
Blocks: btcjson.Int(200),
|
||||||
|
Height: btcjson.Int(-1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getnetworkhashps optional2",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getnetworkhashps", 200, 123)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetNetworkHashPSCmd(btcjson.Int(200), btcjson.Int(123))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[200,123],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetNetworkHashPSCmd{
|
||||||
|
Blocks: btcjson.Int(200),
|
||||||
|
Height: btcjson.Int(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getpeerinfo",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getpeerinfo")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetPeerInfoCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getpeerinfo","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetPeerInfoCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getrawmempool",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getrawmempool")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetRawMempoolCmd(nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getrawmempool","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetRawMempoolCmd{
|
||||||
|
Verbose: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getrawmempool optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getrawmempool", false)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetRawMempoolCmd(btcjson.Bool(false))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getrawmempool","params":[false],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetRawMempoolCmd{
|
||||||
|
Verbose: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getrawtransaction",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getrawtransaction", "123")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetRawTransactionCmd("123", nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getrawtransaction","params":["123"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetRawTransactionCmd{
|
||||||
|
Txid: "123",
|
||||||
|
Verbose: btcjson.Int(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getrawtransaction optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getrawtransaction", "123", 1)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetRawTransactionCmd("123", btcjson.Int(1))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getrawtransaction","params":["123",1],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetRawTransactionCmd{
|
||||||
|
Txid: "123",
|
||||||
|
Verbose: btcjson.Int(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gettxout",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("gettxout", "123", 1)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetTxOutCmd("123", 1, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"gettxout","params":["123",1],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetTxOutCmd{
|
||||||
|
Txid: "123",
|
||||||
|
Vout: 1,
|
||||||
|
IncludeMempool: btcjson.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gettxout optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("gettxout", "123", 1, true)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetTxOutCmd("123", 1, btcjson.Bool(true))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"gettxout","params":["123",1,true],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetTxOutCmd{
|
||||||
|
Txid: "123",
|
||||||
|
Vout: 1,
|
||||||
|
IncludeMempool: btcjson.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gettxoutsetinfo",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("gettxoutsetinfo")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetTxOutSetInfoCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"gettxoutsetinfo","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetTxOutSetInfoCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getwork",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getwork")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetWorkCmd(nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getwork","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetWorkCmd{
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getwork optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getwork", "00112233")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetWorkCmd(btcjson.String("00112233"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getwork","params":["00112233"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetWorkCmd{
|
||||||
|
Data: btcjson.String("00112233"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "help",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("help")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewHelpCmd(nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"help","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.HelpCmd{
|
||||||
|
Command: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "help optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("help", "getblock")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewHelpCmd(btcjson.String("getblock"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"help","params":["getblock"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.HelpCmd{
|
||||||
|
Command: btcjson.String("getblock"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalidateblock",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("invalidateblock", "123")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewInvalidateBlockCmd("123")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"invalidateblock","params":["123"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.InvalidateBlockCmd{
|
||||||
|
BlockHash: "123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ping",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("ping")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewPingCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"ping","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.PingCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reconsiderblock",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("reconsiderblock", "123")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewReconsiderBlockCmd("123")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"reconsiderblock","params":["123"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ReconsiderBlockCmd{
|
||||||
|
BlockHash: "123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "searchrawtransactions",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("searchrawtransactions", "1Address")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSearchRawTransactionsCmd("1Address", nil, nil, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SearchRawTransactionsCmd{
|
||||||
|
Address: "1Address",
|
||||||
|
Verbose: btcjson.Bool(true),
|
||||||
|
Skip: btcjson.Int(0),
|
||||||
|
Count: btcjson.Int(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "searchrawtransactions",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("searchrawtransactions", "1Address", false)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSearchRawTransactionsCmd("1Address",
|
||||||
|
btcjson.Bool(false), nil, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",false],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SearchRawTransactionsCmd{
|
||||||
|
Address: "1Address",
|
||||||
|
Verbose: btcjson.Bool(false),
|
||||||
|
Skip: btcjson.Int(0),
|
||||||
|
Count: btcjson.Int(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "searchrawtransactions",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("searchrawtransactions", "1Address", false, 5)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSearchRawTransactionsCmd("1Address",
|
||||||
|
btcjson.Bool(false), btcjson.Int(5), nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",false,5],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SearchRawTransactionsCmd{
|
||||||
|
Address: "1Address",
|
||||||
|
Verbose: btcjson.Bool(false),
|
||||||
|
Skip: btcjson.Int(5),
|
||||||
|
Count: btcjson.Int(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "searchrawtransactions",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("searchrawtransactions", "1Address", false, 5, 10)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSearchRawTransactionsCmd("1Address",
|
||||||
|
btcjson.Bool(false), btcjson.Int(5), btcjson.Int(10))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",false,5,10],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SearchRawTransactionsCmd{
|
||||||
|
Address: "1Address",
|
||||||
|
Verbose: btcjson.Bool(false),
|
||||||
|
Skip: btcjson.Int(5),
|
||||||
|
Count: btcjson.Int(10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sendrawtransaction",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("sendrawtransaction", "1122")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSendRawTransactionCmd("1122", nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"sendrawtransaction","params":["1122"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SendRawTransactionCmd{
|
||||||
|
HexTx: "1122",
|
||||||
|
AllowHighFees: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sendrawtransaction optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("sendrawtransaction", "1122", false)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSendRawTransactionCmd("1122", btcjson.Bool(false))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"sendrawtransaction","params":["1122",false],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SendRawTransactionCmd{
|
||||||
|
HexTx: "1122",
|
||||||
|
AllowHighFees: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "setgenerate",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("setgenerate", true)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSetGenerateCmd(true, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"setgenerate","params":[true],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SetGenerateCmd{
|
||||||
|
Generate: true,
|
||||||
|
GenProcLimit: btcjson.Int(-1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "setgenerate optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("setgenerate", true, 6)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSetGenerateCmd(true, btcjson.Int(6))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"setgenerate","params":[true,6],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SetGenerateCmd{
|
||||||
|
Generate: true,
|
||||||
|
GenProcLimit: btcjson.Int(6),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stop",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("stop")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewStopCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"stop","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.StopCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "submitblock",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("submitblock", "112233")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSubmitBlockCmd("112233", nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"submitblock","params":["112233"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SubmitBlockCmd{
|
||||||
|
HexBlock: "112233",
|
||||||
|
Options: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "submitblock optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("submitblock", "112233", `{"workid":"12345"}`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
options := btcjson.SubmitBlockOptions{
|
||||||
|
WorkID: "12345",
|
||||||
|
}
|
||||||
|
return btcjson.NewSubmitBlockCmd("112233", &options)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"submitblock","params":["112233",{"workid":"12345"}],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SubmitBlockCmd{
|
||||||
|
HexBlock: "112233",
|
||||||
|
Options: &btcjson.SubmitBlockOptions{
|
||||||
|
WorkID: "12345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validateaddress",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("validateaddress", "1Address")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewValidateAddressCmd("1Address")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"validateaddress","params":["1Address"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ValidateAddressCmd{
|
||||||
|
Address: "1Address",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "verifychain",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("verifychain")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewVerifyChainCmd(nil, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"verifychain","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.VerifyChainCmd{
|
||||||
|
CheckLevel: btcjson.Int32(3),
|
||||||
|
CheckDepth: btcjson.Int32(288),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "verifychain optional1",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("verifychain", 2)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewVerifyChainCmd(btcjson.Int32(2), nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"verifychain","params":[2],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.VerifyChainCmd{
|
||||||
|
CheckLevel: btcjson.Int32(2),
|
||||||
|
CheckDepth: btcjson.Int32(288),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "verifychain optional2",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("verifychain", 2, 500)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewVerifyChainCmd(btcjson.Int32(2), btcjson.Int32(500))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"verifychain","params":[2,500],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.VerifyChainCmd{
|
||||||
|
CheckLevel: btcjson.Int32(2),
|
||||||
|
CheckDepth: btcjson.Int32(500),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "verifymessage",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("verifymessage", "1Address", "301234", "test")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewVerifyMessageCmd("1Address", "301234", "test")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"verifymessage","params":["1Address","301234","test"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.VerifyMessageCmd{
|
||||||
|
Address: "1Address",
|
||||||
|
Signature: "301234",
|
||||||
|
Message: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Marshal the command as created by the new static command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is created without error via the generic
|
||||||
|
// new command creation function.
|
||||||
|
cmd, err := test.newCmd()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||||
|
i, test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the command as created by the generic new command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var request btcjson.Request
|
||||||
|
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||||
|
"unmarshalling JSON-RPC request: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||||
|
"- got %s, want %s", i, test.name,
|
||||||
|
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||||
|
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChainSvrCmdErrors ensures any errors that occur in the command during
|
||||||
|
// custom mashal and unmarshal are as expected.
|
||||||
|
func TestChainSvrCmdErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
result interface{}
|
||||||
|
marshalled string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "template request with invalid type",
|
||||||
|
result: &btcjson.TemplateRequest{},
|
||||||
|
marshalled: `{"mode":1}`,
|
||||||
|
err: &json.UnmarshalTypeError{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid template request sigoplimit field",
|
||||||
|
result: &btcjson.TemplateRequest{},
|
||||||
|
marshalled: `{"sigoplimit":"invalid"}`,
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid template request sizelimit field",
|
||||||
|
result: &btcjson.TemplateRequest{},
|
||||||
|
marshalled: `{"sizelimit":"invalid"}`,
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
err := json.Unmarshal([]byte(test.marshalled), &test.result)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[2]v), "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if terr, ok := test.err.(btcjson.Error); ok {
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != terr.ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||||
|
"- got %v (%v), want %v", i, test.name,
|
||||||
|
gotErrorCode, terr, terr.ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
338
btcjson/v2/btcjson/chainsvrresults.go
Normal file
338
btcjson/v2/btcjson/chainsvrresults.go
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// GetBlockVerboseResult models the data from the getblock command when the
|
||||||
|
// verbose flag is set. When the verbose flag is not set, getblock returns a
|
||||||
|
// hex-encoded string.
|
||||||
|
type GetBlockVerboseResult struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Confirmations uint64 `json:"confirmations"`
|
||||||
|
Size int32 `json:"size"`
|
||||||
|
Height int64 `json:"height"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
MerkleRoot string `json:"merkleroot"`
|
||||||
|
Tx []string `json:"tx,omitempty"`
|
||||||
|
RawTx []TxRawResult `json:"rawtx,omitempty"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
Nonce uint32 `json:"nonce"`
|
||||||
|
Bits string `json:"bits"`
|
||||||
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
PreviousHash string `json:"previousblockhash"`
|
||||||
|
NextHash string `json:"nextblockhash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMultiSigResult models the data returned from the createmultisig
|
||||||
|
// command.
|
||||||
|
type CreateMultiSigResult struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
RedeemScript string `json:"redeemScript"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeScriptResult models the data returned from the decodescript command.
|
||||||
|
type DecodeScriptResult struct {
|
||||||
|
Asm string `json:"asm"`
|
||||||
|
ReqSigs int32 `json:"reqSigs,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Addresses []string `json:"addresses,omitempty"`
|
||||||
|
P2sh string `json:"p2sh"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddedNodeInfoResultAddr models the data of the addresses portion of the
|
||||||
|
// getaddednodeinfo command.
|
||||||
|
type GetAddedNodeInfoResultAddr struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Connected string `json:"connected"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddedNodeInfoResult models the data from the getaddednodeinfo command.
|
||||||
|
type GetAddedNodeInfoResult struct {
|
||||||
|
AddedNode string `json:"addednode"`
|
||||||
|
Connected *bool `json:"connected,omitempty"`
|
||||||
|
Addresses *[]GetAddedNodeInfoResultAddr `json:"addresses,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockChainInfoResult models the data returned from the getblockchaininfo
|
||||||
|
// command.
|
||||||
|
type GetBlockChainInfoResult struct {
|
||||||
|
Chain string `json:"chain"`
|
||||||
|
Blocks int32 `json:"blocks"`
|
||||||
|
Headers int32 `json:"headers"`
|
||||||
|
BestBlockHash string `json:"bestblockhash"`
|
||||||
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
VerificationProgress float64 `json:"verificationprogress"`
|
||||||
|
ChainWork string `json:"chainwork"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTemplateResultTx models the transactions field of the
|
||||||
|
// getblocktemplate command.
|
||||||
|
type GetBlockTemplateResultTx struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Depends []int64 `json:"depends"`
|
||||||
|
Fee int64 `json:"fee"`
|
||||||
|
SigOps int64 `json:"sigops"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTemplateResultAux models the coinbaseaux field of the
|
||||||
|
// getblocktemplate command.
|
||||||
|
type GetBlockTemplateResultAux struct {
|
||||||
|
Flags string `json:"flags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTemplateResult models the data returned from the getblocktemplate
|
||||||
|
// command.
|
||||||
|
type GetBlockTemplateResult struct {
|
||||||
|
// Base fields from BIP 0022. CoinbaseAux is optional. One of
|
||||||
|
// CoinbaseTxn or CoinbaseValue must be specified, but not both.
|
||||||
|
Bits string `json:"bits"`
|
||||||
|
CurTime int64 `json:"curtime"`
|
||||||
|
Height int64 `json:"height"`
|
||||||
|
PreviousHash string `json:"previousblockhash"`
|
||||||
|
SigOpLimit int64 `json:"sigoplimit,omitempty"`
|
||||||
|
SizeLimit int64 `json:"sizelimit,omitempty"`
|
||||||
|
Transactions []GetBlockTemplateResultTx `json:"transactions"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
CoinbaseAux *GetBlockTemplateResultAux `json:"coinbaseaux,omitempty"`
|
||||||
|
CoinbaseTxn *GetBlockTemplateResultTx `json:"coinbasetxn,omitempty"`
|
||||||
|
CoinbaseValue *int64 `json:"coinbasevalue,omitempty"`
|
||||||
|
WorkID string `json:"workid,omitempty"`
|
||||||
|
|
||||||
|
// Optional long polling from BIP 0022.
|
||||||
|
LongPollID string `json:"longpollid,omitempty"`
|
||||||
|
LongPollURI string `json:"longpolluri,omitempty"`
|
||||||
|
SubmitOld *bool `json:"submitold,omitempty"`
|
||||||
|
|
||||||
|
// Basic pool extension from BIP 0023.
|
||||||
|
Target string `json:"target,omitempty"`
|
||||||
|
Expires int64 `json:"expires,omitempty"`
|
||||||
|
|
||||||
|
// Mutations from BIP 0023.
|
||||||
|
MaxTime int64 `json:"maxtime,omitempty"`
|
||||||
|
MinTime int64 `json:"mintime,omitempty"`
|
||||||
|
Mutable []string `json:"mutable,omitempty"`
|
||||||
|
NonceRange string `json:"noncerange,omitempty"`
|
||||||
|
|
||||||
|
// Block proposal from BIP 0023.
|
||||||
|
Capabilities []string `json:"capabilities,omitempty"`
|
||||||
|
RejectReasion string `json:"reject-reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkInfoResult models the data returned from the getnetworkinfo
|
||||||
|
// command.
|
||||||
|
type GetNetworkInfoResult struct {
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
ProtocolVersion int32 `json:"protocolversion"`
|
||||||
|
TimeOffset int64 `json:"timeoffset"`
|
||||||
|
Connections int32 `json:"connections"`
|
||||||
|
Networks []NetworksResult `json:"networks"`
|
||||||
|
RelayFee float64 `json:"relayfee"`
|
||||||
|
LocalAddresses []LocalAddressesResult `json:"localaddresses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeerInfoResult models the data returned from the getpeerinfo command.
|
||||||
|
type GetPeerInfoResult struct {
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
AddrLocal string `json:"addrlocal,omitempty"`
|
||||||
|
Services string `json:"services"`
|
||||||
|
LastSend int64 `json:"lastsend"`
|
||||||
|
LastRecv int64 `json:"lastrecv"`
|
||||||
|
BytesSent uint64 `json:"bytessent"`
|
||||||
|
BytesRecv uint64 `json:"bytesrecv"`
|
||||||
|
PingTime float64 `json:"pingtime"`
|
||||||
|
PingWait float64 `json:"pingwait,omitempty"`
|
||||||
|
ConnTime int64 `json:"conntime"`
|
||||||
|
Version uint32 `json:"version"`
|
||||||
|
SubVer string `json:"subver"`
|
||||||
|
Inbound bool `json:"inbound"`
|
||||||
|
StartingHeight int32 `json:"startingheight"`
|
||||||
|
CurrentHeight int32 `json:"currentheight,omitempty"`
|
||||||
|
BanScore int32 `json:"banscore"`
|
||||||
|
SyncNode bool `json:"syncnode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawMempoolVerboseResult models the data returned from the getrawmempool
|
||||||
|
// command when the verbose flag is set. When the verbose flag is not set,
|
||||||
|
// getrawmempool returns an array of transaction hashes.
|
||||||
|
type GetRawMempoolVerboseResult struct {
|
||||||
|
Size int32 `json:"size"`
|
||||||
|
Fee float64 `json:"fee"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
Height int64 `json:"height"`
|
||||||
|
StartingPriority float64 `json:"startingpriority"`
|
||||||
|
CurrentPriority float64 `json:"currentpriority"`
|
||||||
|
Depends []string `json:"depends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptPubKeyResult models the scriptPubKey data of a tx script. It is
|
||||||
|
// defined separately since it is used by multiple commands.
|
||||||
|
type ScriptPubKeyResult struct {
|
||||||
|
Asm string `json:"asm"`
|
||||||
|
Hex string `json:"hex,omitempty"`
|
||||||
|
ReqSigs int32 `json:"reqSigs,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Addresses []string `json:"addresses,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxOutResult models the data from the gettxout command.
|
||||||
|
type GetTxOutResult struct {
|
||||||
|
BestBlock string `json:"bestblock"`
|
||||||
|
Confirmations int64 `json:"confirmations"`
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
Coinbase bool `json:"coinbase"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetTotalsResult models the data returned from the getnettotals command.
|
||||||
|
type GetNetTotalsResult struct {
|
||||||
|
TotalBytesRecv uint64 `json:"totalbytesrecv"`
|
||||||
|
TotalBytesSent uint64 `json:"totalbytessent"`
|
||||||
|
TimeMillis int64 `json:"timemillis"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptSig models a signature script. It is defined seperately since it only
|
||||||
|
// applies to non-coinbase. Therefore the field in the Vin structure needs
|
||||||
|
// to be a pointer.
|
||||||
|
type ScriptSig struct {
|
||||||
|
Asm string `json:"asm"`
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vin models parts of the tx data. It is defined seperately since
|
||||||
|
// getrawtransaction, decoderawtransaction, and searchrawtransaction use the
|
||||||
|
// same structure.
|
||||||
|
type Vin struct {
|
||||||
|
Coinbase string `json:"coinbase"`
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||||
|
Sequence uint32 `json:"sequence"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCoinBase returns a bool to show if a Vin is a Coinbase one or not.
|
||||||
|
func (v *Vin) IsCoinBase() bool {
|
||||||
|
return len(v.Coinbase) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON provides a custom Marshal method for Vin.
|
||||||
|
func (v *Vin) MarshalJSON() ([]byte, error) {
|
||||||
|
if v.IsCoinBase() {
|
||||||
|
coinbaseStruct := struct {
|
||||||
|
Coinbase string `json:"coinbase"`
|
||||||
|
Sequence uint32 `json:"sequence"`
|
||||||
|
}{
|
||||||
|
Coinbase: v.Coinbase,
|
||||||
|
Sequence: v.Sequence,
|
||||||
|
}
|
||||||
|
return json.Marshal(coinbaseStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
txStruct := struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||||
|
Sequence uint32 `json:"sequence"`
|
||||||
|
}{
|
||||||
|
Txid: v.Txid,
|
||||||
|
Vout: v.Vout,
|
||||||
|
ScriptSig: v.ScriptSig,
|
||||||
|
Sequence: v.Sequence,
|
||||||
|
}
|
||||||
|
return json.Marshal(txStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 uint32 `json:"n"`
|
||||||
|
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMiningInfoResult models the data from the getmininginfo command.
|
||||||
|
type GetMiningInfoResult struct {
|
||||||
|
Blocks int64 `json:"blocks"`
|
||||||
|
CurrentBlockSize uint64 `json:"currentblocksize"`
|
||||||
|
CurrentBlockTx uint64 `json:"currentblocktx"`
|
||||||
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
Errors string `json:"errors"`
|
||||||
|
Generate bool `json:"generate"`
|
||||||
|
GenProcLimit int32 `json:"genproclimit"`
|
||||||
|
HashesPerSec int64 `json:"hashespersec"`
|
||||||
|
NetworkHashPS int64 `json:"networkhashps"`
|
||||||
|
PooledTx uint64 `json:"pooledtx"`
|
||||||
|
TestNet bool `json:"testnet"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWorkResult models the data from the getwork command.
|
||||||
|
type GetWorkResult struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
Hash1 string `json:"hash1"`
|
||||||
|
Midstate string `json:"midstate"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoChainResult models the data returned by the chain server getinfo command.
|
||||||
|
type InfoChainResult struct {
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
ProtocolVersion int32 `json:"protocolversion"`
|
||||||
|
Blocks int32 `json:"blocks"`
|
||||||
|
TimeOffset int64 `json:"timeoffset"`
|
||||||
|
Connections int32 `json:"connections"`
|
||||||
|
Proxy string `json:"proxy"`
|
||||||
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
TestNet bool `json:"testnet"`
|
||||||
|
RelayFee float64 `json:"relayfee"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddressesResult models the localaddresses data from the getnetworkinfo
|
||||||
|
// command.
|
||||||
|
type LocalAddressesResult struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Score int32 `json:"score"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworksResult models the networks data from the getnetworkinfo command.
|
||||||
|
type NetworksResult struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Limited bool `json:"limited"`
|
||||||
|
Reachable bool `json:"reachable"`
|
||||||
|
Proxy string `json:"proxy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxRawResult models the data from the getrawtransaction and
|
||||||
|
// searchrawtransaction commands.
|
||||||
|
type TxRawResult struct {
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
LockTime uint32 `json:"locktime"`
|
||||||
|
Vin []Vin `json:"vin"`
|
||||||
|
Vout []Vout `json:"vout"`
|
||||||
|
BlockHash string `json:"blockhash,omitempty"`
|
||||||
|
Confirmations uint64 `json:"confirmations"`
|
||||||
|
Time int64 `json:"time,omitempty"`
|
||||||
|
Blocktime int64 `json:"blocktime,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxRawDecodeResult models the data from the decoderawtransaction command.
|
||||||
|
type TxRawDecodeResult struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
Locktime uint32 `json:"locktime"`
|
||||||
|
Vin []Vin `json:"vin"`
|
||||||
|
Vout []Vout `json:"vout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAddressChainResult models the data returned by the chain server
|
||||||
|
// validateaddress command.
|
||||||
|
type ValidateAddressChainResult struct {
|
||||||
|
IsValid bool `json:"isvalid"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
}
|
63
btcjson/v2/btcjson/chainsvrresults_test.go
Normal file
63
btcjson/v2/btcjson/chainsvrresults_test.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestChainSvrCustomResults ensures any results that have custom marshalling
|
||||||
|
// work as inteded.
|
||||||
|
// and unmarshal code of results are as expected.
|
||||||
|
func TestChainSvrCustomResults(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
result interface{}
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "custom vin marshal with coinbase",
|
||||||
|
result: &btcjson.Vin{
|
||||||
|
Coinbase: "021234",
|
||||||
|
Sequence: 4294967295,
|
||||||
|
},
|
||||||
|
expected: `{"coinbase":"021234","sequence":4294967295}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom vin marshal without coinbase",
|
||||||
|
result: &btcjson.Vin{
|
||||||
|
Txid: "123",
|
||||||
|
Vout: 1,
|
||||||
|
ScriptSig: &btcjson.ScriptSig{
|
||||||
|
Asm: "0",
|
||||||
|
Hex: "00",
|
||||||
|
},
|
||||||
|
Sequence: 4294967295,
|
||||||
|
},
|
||||||
|
expected: `{"txid":"123","vout":1,"scriptSig":{"asm":"0","hex":"00"},"sequence":4294967295}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
marshalled, err := json.Marshal(test.result)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if string(marshalled) != test.expected {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marhsalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
btcjson/v2/btcjson/chainsvrwscmds.go
Normal file
128
btcjson/v2/btcjson/chainsvrwscmds.go
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||||
|
// a chain server, but are only available via websockets.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthenticateCmd defines the authenticate JSON-RPC command.
|
||||||
|
type AuthenticateCmd struct {
|
||||||
|
Username string
|
||||||
|
Passphrase string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthenticateCmd returns a new instance which can be used to issue an
|
||||||
|
// authenticate JSON-RPC command.
|
||||||
|
func NewAuthenticateCmd(username, passphrase string) *AuthenticateCmd {
|
||||||
|
return &AuthenticateCmd{
|
||||||
|
Username: username,
|
||||||
|
Passphrase: passphrase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyBlocksCmd defines the notifyblocks JSON-RPC command.
|
||||||
|
type NotifyBlocksCmd struct{}
|
||||||
|
|
||||||
|
// NewNotifyBlocksCmd returns a new instance which can be used to issue a
|
||||||
|
// notifyblocks JSON-RPC command.
|
||||||
|
func NewNotifyBlocksCmd() *NotifyBlocksCmd {
|
||||||
|
return &NotifyBlocksCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyNewTransactionsCmd defines the notifynewtransactions JSON-RPC command.
|
||||||
|
type NotifyNewTransactionsCmd struct {
|
||||||
|
Verbose *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotifyNewTransactionsCmd returns a new instance which can be used to issue
|
||||||
|
// a notifynewtransactions JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewNotifyNewTransactionsCmd(verbose *bool) *NotifyNewTransactionsCmd {
|
||||||
|
return &NotifyNewTransactionsCmd{
|
||||||
|
Verbose: verbose,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyReceivedCmd defines the notifyreceived JSON-RPC command.
|
||||||
|
type NotifyReceivedCmd struct {
|
||||||
|
Addresses []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotifyReceivedCmd returns a new instance which can be used to issue a
|
||||||
|
// notifyreceived JSON-RPC command.
|
||||||
|
func NewNotifyReceivedCmd(addresses []string) *NotifyReceivedCmd {
|
||||||
|
return &NotifyReceivedCmd{
|
||||||
|
Addresses: addresses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutPoint describes a transaction outpoint that will be marshalled to and
|
||||||
|
// from JSON.
|
||||||
|
type OutPoint struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Index uint32 `json:"index"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOutPointFromWire creates a new OutPoint from the OutPoint structure
|
||||||
|
// of the btcwire package.
|
||||||
|
func NewOutPointFromWire(op *wire.OutPoint) *OutPoint {
|
||||||
|
return &OutPoint{
|
||||||
|
Hash: op.Hash.String(),
|
||||||
|
Index: op.Index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifySpentCmd defines the notifyspent JSON-RPC command.
|
||||||
|
type NotifySpentCmd struct {
|
||||||
|
OutPoints []OutPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotifySpentCmd returns a new instance which can be used to issue a
|
||||||
|
// notifyspent JSON-RPC command.
|
||||||
|
func NewNotifySpentCmd(outPoints []OutPoint) *NotifySpentCmd {
|
||||||
|
return &NotifySpentCmd{
|
||||||
|
OutPoints: outPoints,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RescanCmd defines the rescan JSON-RPC command.
|
||||||
|
type RescanCmd struct {
|
||||||
|
BeginBlock string
|
||||||
|
Addresses []string
|
||||||
|
OutPoints []OutPoint
|
||||||
|
EndBlock *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRescanCmd returns a new instance which can be used to issue a rescan
|
||||||
|
// JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewRescanCmd(beginBlock string, addresses []string, outPoints []OutPoint, endBlock *string) *RescanCmd {
|
||||||
|
return &RescanCmd{
|
||||||
|
BeginBlock: beginBlock,
|
||||||
|
Addresses: addresses,
|
||||||
|
OutPoints: outPoints,
|
||||||
|
EndBlock: endBlock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// The commands in this file are only usable by websockets.
|
||||||
|
flags := UFWebsocketOnly
|
||||||
|
|
||||||
|
MustRegisterCmd("authenticate", (*AuthenticateCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("notifyblocks", (*NotifyBlocksCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("notifynewtransactions", (*NotifyNewTransactionsCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("notifyreceived", (*NotifyReceivedCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("notifyspent", (*NotifySpentCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("rescan", (*RescanCmd)(nil), flags)
|
||||||
|
}
|
213
btcjson/v2/btcjson/chainsvrwscmds_test.go
Normal file
213
btcjson/v2/btcjson/chainsvrwscmds_test.go
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestChainSvrWsCmds tests all of the chain server websocket-specific commands
|
||||||
|
// marshal and unmarshal into valid results include handling of optional fields
|
||||||
|
// being omitted in the marshalled command, while optional fields with defaults
|
||||||
|
// have the default assigned on unmarshalled commands.
|
||||||
|
func TestChainSvrWsCmds(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testID := int(1)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
newCmd func() (interface{}, error)
|
||||||
|
staticCmd func() interface{}
|
||||||
|
marshalled string
|
||||||
|
unmarshalled interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "authenticate",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("authenticate", "user", "pass")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewAuthenticateCmd("user", "pass")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"authenticate","params":["user","pass"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.AuthenticateCmd{Username: "user", Passphrase: "pass"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notifyblocks",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("notifyblocks")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewNotifyBlocksCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"notifyblocks","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.NotifyBlocksCmd{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notifynewtransactions",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("notifynewtransactions")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewNotifyNewTransactionsCmd(nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"notifynewtransactions","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.NotifyNewTransactionsCmd{
|
||||||
|
Verbose: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notifynewtransactions optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("notifynewtransactions", true)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewNotifyNewTransactionsCmd(btcjson.Bool(true))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"notifynewtransactions","params":[true],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.NotifyNewTransactionsCmd{
|
||||||
|
Verbose: btcjson.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notifyreceived",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("notifyreceived", []string{"1Address"})
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewNotifyReceivedCmd([]string{"1Address"})
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"notifyreceived","params":[["1Address"]],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.NotifyReceivedCmd{
|
||||||
|
Addresses: []string{"1Address"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notifyspent",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("notifyspent", `[{"hash":"123","index":0}]`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
ops := []btcjson.OutPoint{{Hash: "123", Index: 0}}
|
||||||
|
return btcjson.NewNotifySpentCmd(ops)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"notifyspent","params":[[{"hash":"123","index":0}]],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.NotifySpentCmd{
|
||||||
|
OutPoints: []btcjson.OutPoint{{Hash: "123", Index: 0}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rescan",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("rescan", "123", `["1Address"]`, `[{"hash":"0000000000000000000000000000000000000000000000000000000000000123","index":0}]`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
addrs := []string{"1Address"}
|
||||||
|
hash, _ := wire.NewShaHashFromStr("123")
|
||||||
|
op := wire.NewOutPoint(hash, 0)
|
||||||
|
ops := []btcjson.OutPoint{*btcjson.NewOutPointFromWire(op)}
|
||||||
|
return btcjson.NewRescanCmd("123", addrs, ops, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"rescan","params":["123",["1Address"],[{"hash":"0000000000000000000000000000000000000000000000000000000000000123","index":0}]],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.RescanCmd{
|
||||||
|
BeginBlock: "123",
|
||||||
|
Addresses: []string{"1Address"},
|
||||||
|
OutPoints: []btcjson.OutPoint{{Hash: "0000000000000000000000000000000000000000000000000000000000000123", Index: 0}},
|
||||||
|
EndBlock: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rescan optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("rescan", "123", `["1Address"]`, `[{"hash":"123","index":0}]`, "456")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
addrs := []string{"1Address"}
|
||||||
|
ops := []btcjson.OutPoint{{Hash: "123", Index: 0}}
|
||||||
|
return btcjson.NewRescanCmd("123", addrs, ops, btcjson.String("456"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"rescan","params":["123",["1Address"],[{"hash":"123","index":0}],"456"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.RescanCmd{
|
||||||
|
BeginBlock: "123",
|
||||||
|
Addresses: []string{"1Address"},
|
||||||
|
OutPoints: []btcjson.OutPoint{{Hash: "123", Index: 0}},
|
||||||
|
EndBlock: btcjson.String("456"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Marshal the command as created by the new static command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is created without error via the generic
|
||||||
|
// new command creation function.
|
||||||
|
cmd, err := test.newCmd()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||||
|
i, test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the command as created by the generic new command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var request btcjson.Request
|
||||||
|
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||||
|
"unmarshalling JSON-RPC request: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||||
|
"- got %s, want %s", i, test.name,
|
||||||
|
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||||
|
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
192
btcjson/v2/btcjson/chainsvrwsntfns.go
Normal file
192
btcjson/v2/btcjson/chainsvrwsntfns.go
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// NOTE: This file is intended to house the RPC websocket notifications that are
|
||||||
|
// supported by a chain server.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BlockConnectedNtfnMethod is the method used for notifications from
|
||||||
|
// the chain server that a block has been connected.
|
||||||
|
BlockConnectedNtfnMethod = "blockconnected"
|
||||||
|
|
||||||
|
// BlockDisconnectedNtfnMethod is the method used for notifications from
|
||||||
|
// the chain server that a block has been disconnected.
|
||||||
|
BlockDisconnectedNtfnMethod = "blockdisconnected"
|
||||||
|
|
||||||
|
// RecvTxNtfnMethod is the method used for notifications from the chain
|
||||||
|
// server that a transaction which pays to a registered address has been
|
||||||
|
// processed.
|
||||||
|
RecvTxNtfnMethod = "recvtx"
|
||||||
|
|
||||||
|
// RedeemingTxNtfnMethod is the method used for notifications from the
|
||||||
|
// chain server that a transaction which spends a registered outpoint
|
||||||
|
// has been processed.
|
||||||
|
RedeemingTxNtfnMethod = "redeemingtx"
|
||||||
|
|
||||||
|
// RescanFinishedNtfnMethod is the method used for notifications from
|
||||||
|
// the chain server that a rescan operation has finished.
|
||||||
|
RescanFinishedNtfnMethod = "rescanfinished"
|
||||||
|
|
||||||
|
// RescanProgressNtfnMethod is the method used for notifications from
|
||||||
|
// the chain server that a rescan operation this is underway has made
|
||||||
|
// progress.
|
||||||
|
RescanProgressNtfnMethod = "rescanprogress"
|
||||||
|
|
||||||
|
// TxAcceptedNtfnMethod is the method used for notifications from the
|
||||||
|
// chain server that a transaction has been accepted into the mempool.
|
||||||
|
TxAcceptedNtfnMethod = "txaccepted"
|
||||||
|
|
||||||
|
// TxAcceptedVerboseNtfnMethod is the method used for notifications from
|
||||||
|
// the chain server that a transaction has been accepted into the
|
||||||
|
// mempool. This differs from TxAcceptedNtfnMethod in that it provides
|
||||||
|
// more details in the notification.
|
||||||
|
TxAcceptedVerboseNtfnMethod = "txacceptedverbose"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockConnectedNtfn defines the blockconnected JSON-RPC notification.
|
||||||
|
type BlockConnectedNtfn struct {
|
||||||
|
Hash string
|
||||||
|
Height int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockConnectedNtfn returns a new instance which can be used to issue a
|
||||||
|
// blockconnected JSON-RPC notification.
|
||||||
|
func NewBlockConnectedNtfn(hash string, height int32) *BlockConnectedNtfn {
|
||||||
|
return &BlockConnectedNtfn{
|
||||||
|
Hash: hash,
|
||||||
|
Height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockDisconnectedNtfn defines the blockdisconnected JSON-RPC notification.
|
||||||
|
type BlockDisconnectedNtfn struct {
|
||||||
|
Hash string
|
||||||
|
Height int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockDisconnectedNtfn returns a new instance which can be used to issue a
|
||||||
|
// blockdisconnected JSON-RPC notification.
|
||||||
|
func NewBlockDisconnectedNtfn(hash string, height int32) *BlockDisconnectedNtfn {
|
||||||
|
return &BlockDisconnectedNtfn{
|
||||||
|
Hash: hash,
|
||||||
|
Height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockDetails describes details of a tx in a block.
|
||||||
|
type BlockDetails struct {
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Index int `json:"index"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecvTxNtfn defines the recvtx JSON-RPC notification.
|
||||||
|
type RecvTxNtfn struct {
|
||||||
|
HexTx string
|
||||||
|
Block BlockDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecvTxNtfn returns a new instance which can be used to issue a recvtx
|
||||||
|
// JSON-RPC notification.
|
||||||
|
func NewRecvTxNtfn(hexTx string, block BlockDetails) *RecvTxNtfn {
|
||||||
|
return &RecvTxNtfn{
|
||||||
|
HexTx: hexTx,
|
||||||
|
Block: block,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedeemingTxNtfn defines the redeemingtx JSON-RPC notification.
|
||||||
|
type RedeemingTxNtfn struct {
|
||||||
|
HexTx string
|
||||||
|
Block BlockDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRedeemingTxNtfn returns a new instance which can be used to issue a
|
||||||
|
// redeemingtx JSON-RPC notification.
|
||||||
|
func NewRedeemingTxNtfn(hexTx string, block BlockDetails) *RedeemingTxNtfn {
|
||||||
|
return &RedeemingTxNtfn{
|
||||||
|
HexTx: hexTx,
|
||||||
|
Block: block,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RescanFinishedNtfn defines the rescanfinished JSON-RPC notification.
|
||||||
|
type RescanFinishedNtfn struct {
|
||||||
|
Hash string
|
||||||
|
Height int32
|
||||||
|
Time int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRescanFinishedNtfn returns a new instance which can be used to issue a
|
||||||
|
// rescanfinished JSON-RPC notification.
|
||||||
|
func NewRescanFinishedNtfn(hash string, height int32, time int64) *RescanFinishedNtfn {
|
||||||
|
return &RescanFinishedNtfn{
|
||||||
|
Hash: hash,
|
||||||
|
Height: height,
|
||||||
|
Time: time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RescanProgressNtfn defines the rescanprogress JSON-RPC notification.
|
||||||
|
type RescanProgressNtfn struct {
|
||||||
|
Hash string
|
||||||
|
Height int32
|
||||||
|
Time int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRescanProgressNtfn returns a new instance which can be used to issue a
|
||||||
|
// rescanprogress JSON-RPC notification.
|
||||||
|
func NewRescanProgressNtfn(hash string, height int32, time int64) *RescanProgressNtfn {
|
||||||
|
return &RescanProgressNtfn{
|
||||||
|
Hash: hash,
|
||||||
|
Height: height,
|
||||||
|
Time: time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxAcceptedNtfn defines the txaccepted JSON-RPC notification.
|
||||||
|
type TxAcceptedNtfn struct {
|
||||||
|
TxID string
|
||||||
|
Amount float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTxAcceptedNtfn returns a new instance which can be used to issue a
|
||||||
|
// txaccepted JSON-RPC notification.
|
||||||
|
func NewTxAcceptedNtfn(txHash string, amount float64) *TxAcceptedNtfn {
|
||||||
|
return &TxAcceptedNtfn{
|
||||||
|
TxID: txHash,
|
||||||
|
Amount: amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxAcceptedVerboseNtfn defines the txacceptedverbose JSON-RPC notification.
|
||||||
|
type TxAcceptedVerboseNtfn struct {
|
||||||
|
RawTx TxRawResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTxAcceptedVerboseNtfn returns a new instance which can be used to issue a
|
||||||
|
// txacceptedverbose JSON-RPC notification.
|
||||||
|
func NewTxAcceptedVerboseNtfn(rawTx TxRawResult) *TxAcceptedVerboseNtfn {
|
||||||
|
return &TxAcceptedVerboseNtfn{
|
||||||
|
RawTx: rawTx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// The commands in this file are only usable by websockets and are
|
||||||
|
// notifications.
|
||||||
|
flags := UFWebsocketOnly | UFNotification
|
||||||
|
|
||||||
|
MustRegisterCmd(BlockConnectedNtfnMethod, (*BlockConnectedNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(BlockDisconnectedNtfnMethod, (*BlockDisconnectedNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(RecvTxNtfnMethod, (*RecvTxNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(RedeemingTxNtfnMethod, (*RedeemingTxNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(RescanFinishedNtfnMethod, (*RescanFinishedNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(RescanProgressNtfnMethod, (*RescanProgressNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(TxAcceptedNtfnMethod, (*TxAcceptedNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(TxAcceptedVerboseNtfnMethod, (*TxAcceptedVerboseNtfn)(nil), flags)
|
||||||
|
}
|
251
btcjson/v2/btcjson/chainsvrwsntfns_test.go
Normal file
251
btcjson/v2/btcjson/chainsvrwsntfns_test.go
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestChainSvrWsNtfns tests all of the chain server websocket-specific
|
||||||
|
// notifications marshal and unmarshal into valid results include handling of
|
||||||
|
// optional fields being omitted in the marshalled command, while optional
|
||||||
|
// fields with defaults have the default assigned on unmarshalled commands.
|
||||||
|
func TestChainSvrWsNtfns(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
newNtfn func() (interface{}, error)
|
||||||
|
staticNtfn func() interface{}
|
||||||
|
marshalled string
|
||||||
|
unmarshalled interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "blockconnected",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("blockconnected", "123", 100000)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
return btcjson.NewBlockConnectedNtfn("123", 100000)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"blockconnected","params":["123",100000],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.BlockConnectedNtfn{
|
||||||
|
Hash: "123",
|
||||||
|
Height: 100000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blockdisconnected",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("blockdisconnected", "123", 100000)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
return btcjson.NewBlockDisconnectedNtfn("123", 100000)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"blockdisconnected","params":["123",100000],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.BlockDisconnectedNtfn{
|
||||||
|
Hash: "123",
|
||||||
|
Height: 100000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "recvtx",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("recvtx", "001122", `{"height":100000,"hash":"123","index":0,"time":12345678}`)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
blockDetails := btcjson.BlockDetails{
|
||||||
|
Height: 100000,
|
||||||
|
Hash: "123",
|
||||||
|
Index: 0,
|
||||||
|
Time: 12345678,
|
||||||
|
}
|
||||||
|
return btcjson.NewRecvTxNtfn("001122", blockDetails)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"recvtx","params":["001122",{"height":100000,"hash":"123","index":0,"time":12345678}],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.RecvTxNtfn{
|
||||||
|
HexTx: "001122",
|
||||||
|
Block: btcjson.BlockDetails{
|
||||||
|
Height: 100000,
|
||||||
|
Hash: "123",
|
||||||
|
Index: 0,
|
||||||
|
Time: 12345678,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redeemingtx",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("redeemingtx", "001122", `{"height":100000,"hash":"123","index":0,"time":12345678}`)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
blockDetails := btcjson.BlockDetails{
|
||||||
|
Height: 100000,
|
||||||
|
Hash: "123",
|
||||||
|
Index: 0,
|
||||||
|
Time: 12345678,
|
||||||
|
}
|
||||||
|
return btcjson.NewRedeemingTxNtfn("001122", blockDetails)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"redeemingtx","params":["001122",{"height":100000,"hash":"123","index":0,"time":12345678}],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.RedeemingTxNtfn{
|
||||||
|
HexTx: "001122",
|
||||||
|
Block: btcjson.BlockDetails{
|
||||||
|
Height: 100000,
|
||||||
|
Hash: "123",
|
||||||
|
Index: 0,
|
||||||
|
Time: 12345678,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rescanfinished",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("rescanfinished", "123", 100000, 12345678)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
return btcjson.NewRescanFinishedNtfn("123", 100000, 12345678)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"rescanfinished","params":["123",100000,12345678],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.RescanFinishedNtfn{
|
||||||
|
Hash: "123",
|
||||||
|
Height: 100000,
|
||||||
|
Time: 12345678,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rescanprogress",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("rescanprogress", "123", 100000, 12345678)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
return btcjson.NewRescanProgressNtfn("123", 100000, 12345678)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"rescanprogress","params":["123",100000,12345678],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.RescanProgressNtfn{
|
||||||
|
Hash: "123",
|
||||||
|
Height: 100000,
|
||||||
|
Time: 12345678,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "txaccepted",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("txaccepted", "123", 1.5)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
return btcjson.NewTxAcceptedNtfn("123", 1.5)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"txaccepted","params":["123",1.5],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.TxAcceptedNtfn{
|
||||||
|
TxID: "123",
|
||||||
|
Amount: 1.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "txacceptedverbose",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("txacceptedverbose", `{"hex":"001122","txid":"123","version":1,"locktime":4294967295,"vin":null,"vout":null,"confirmations":0}`)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
txResult := btcjson.TxRawResult{
|
||||||
|
Hex: "001122",
|
||||||
|
Txid: "123",
|
||||||
|
Version: 1,
|
||||||
|
LockTime: 4294967295,
|
||||||
|
Vin: nil,
|
||||||
|
Vout: nil,
|
||||||
|
Confirmations: 0,
|
||||||
|
}
|
||||||
|
return btcjson.NewTxAcceptedVerboseNtfn(txResult)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"txacceptedverbose","params":[{"hex":"001122","txid":"123","version":1,"locktime":4294967295,"vin":null,"vout":null,"confirmations":0}],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.TxAcceptedVerboseNtfn{
|
||||||
|
RawTx: btcjson.TxRawResult{
|
||||||
|
Hex: "001122",
|
||||||
|
Txid: "123",
|
||||||
|
Version: 1,
|
||||||
|
LockTime: 4294967295,
|
||||||
|
Vin: nil,
|
||||||
|
Vout: nil,
|
||||||
|
Confirmations: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Marshal the notification as created by the new static
|
||||||
|
// creation function. The ID is nil for notifications.
|
||||||
|
marshalled, err := btcjson.MarshalCmd(nil, test.staticNtfn())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the notification is created without error via the
|
||||||
|
// generic new notification creation function.
|
||||||
|
cmd, err := test.newNtfn()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||||
|
i, test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the notification as created by the generic new
|
||||||
|
// notification creation function. The ID is nil for
|
||||||
|
// notifications.
|
||||||
|
marshalled, err = btcjson.MarshalCmd(nil, cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var request btcjson.Request
|
||||||
|
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||||
|
"unmarshalling JSON-RPC request: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||||
|
"- got %s, want %s", i, test.name,
|
||||||
|
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||||
|
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
249
btcjson/v2/btcjson/cmdinfo.go
Normal file
249
btcjson/v2/btcjson/cmdinfo.go
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
// Copyright (c) 2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdMethod returns the method for the passed command. The provided command
|
||||||
|
// type must be a registered type. All commands provided by this package are
|
||||||
|
// registered by default.
|
||||||
|
func CmdMethod(cmd interface{}) (string, error) {
|
||||||
|
// Look up the cmd type and error out if not registered.
|
||||||
|
rt := reflect.TypeOf(cmd)
|
||||||
|
registerLock.RLock()
|
||||||
|
method, ok := concreteTypeToMethod[rt]
|
||||||
|
registerLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
str := fmt.Sprintf("%q is not registered", method)
|
||||||
|
return "", makeError(ErrUnregisteredMethod, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return method, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodUsageFlags returns the usage flags for the passed command method. The
|
||||||
|
// provided method must be associated with a registered type. All commands
|
||||||
|
// provided by this package are registered by default.
|
||||||
|
func MethodUsageFlags(method string) (UsageFlag, error) {
|
||||||
|
// Look up details about the provided method and error out if not
|
||||||
|
// registered.
|
||||||
|
registerLock.RLock()
|
||||||
|
info, ok := methodToInfo[method]
|
||||||
|
registerLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
str := fmt.Sprintf("%q is not registered", method)
|
||||||
|
return 0, makeError(ErrUnregisteredMethod, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return info.flags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// subStructUsage returns a string for use in the one-line usage for the given
|
||||||
|
// sub struct. Note that this is specifically for fields which consist of
|
||||||
|
// structs (or an array/slice of structs) as opposed to the top-level command
|
||||||
|
// struct.
|
||||||
|
//
|
||||||
|
// Any fields that include a jsonrpcusage struct tag will use that instead of
|
||||||
|
// being automatically generated.
|
||||||
|
func subStructUsage(structType reflect.Type) string {
|
||||||
|
numFields := structType.NumField()
|
||||||
|
fieldUsages := make([]string, 0, numFields)
|
||||||
|
for i := 0; i < structType.NumField(); i++ {
|
||||||
|
rtf := structType.Field(i)
|
||||||
|
|
||||||
|
// When the field has a jsonrpcusage struct tag specified use
|
||||||
|
// that instead of automatically generating it.
|
||||||
|
if tag := rtf.Tag.Get("jsonrpcusage"); tag != "" {
|
||||||
|
fieldUsages = append(fieldUsages, tag)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the name/value entry for the field while considering
|
||||||
|
// the type of the field. Not all possibile types are covered
|
||||||
|
// here and when one of the types not specifically covered is
|
||||||
|
// encountered, the field name is simply reused for the value.
|
||||||
|
fieldName := strings.ToLower(rtf.Name)
|
||||||
|
fieldValue := fieldName
|
||||||
|
fieldKind := rtf.Type.Kind()
|
||||||
|
switch {
|
||||||
|
case isNumeric(fieldKind):
|
||||||
|
if fieldKind == reflect.Float32 || fieldKind == reflect.Float64 {
|
||||||
|
fieldValue = "n.nnn"
|
||||||
|
} else {
|
||||||
|
fieldValue = "n"
|
||||||
|
}
|
||||||
|
case fieldKind == reflect.String:
|
||||||
|
fieldValue = `"value"`
|
||||||
|
|
||||||
|
case fieldKind == reflect.Struct:
|
||||||
|
fieldValue = subStructUsage(rtf.Type)
|
||||||
|
|
||||||
|
case fieldKind == reflect.Array || fieldKind == reflect.Slice:
|
||||||
|
fieldValue = subArrayUsage(rtf.Type, fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
usage := fmt.Sprintf("%q:%s", fieldName, fieldValue)
|
||||||
|
fieldUsages = append(fieldUsages, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("{%s}", strings.Join(fieldUsages, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
// subArrayUsage returns a string for use in the one-line usage for the given
|
||||||
|
// array or slice. It also contains logic to convert plural field names to
|
||||||
|
// singular so the generated usage string reads better.
|
||||||
|
func subArrayUsage(arrayType reflect.Type, fieldName string) string {
|
||||||
|
// Convert plural field names to singular. Only works for English.
|
||||||
|
singularFieldName := fieldName
|
||||||
|
if strings.HasSuffix(fieldName, "ies") {
|
||||||
|
singularFieldName = strings.TrimSuffix(fieldName, "ies")
|
||||||
|
singularFieldName = singularFieldName + "y"
|
||||||
|
} else if strings.HasSuffix(fieldName, "es") {
|
||||||
|
singularFieldName = strings.TrimSuffix(fieldName, "es")
|
||||||
|
} else if strings.HasSuffix(fieldName, "s") {
|
||||||
|
singularFieldName = strings.TrimSuffix(fieldName, "s")
|
||||||
|
}
|
||||||
|
|
||||||
|
elemType := arrayType.Elem()
|
||||||
|
switch elemType.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return fmt.Sprintf("[%q,...]", singularFieldName)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
return fmt.Sprintf("[%s,...]", subStructUsage(elemType))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to simply showing the field name in array syntax.
|
||||||
|
return fmt.Sprintf(`[%s,...]`, singularFieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldUsage returns a string for use in the one-line usage for the struct
|
||||||
|
// field of a command.
|
||||||
|
//
|
||||||
|
// Any fields that include a jsonrpcusage struct tag will use that instead of
|
||||||
|
// being automatically generated.
|
||||||
|
func fieldUsage(structField reflect.StructField, defaultVal *reflect.Value) string {
|
||||||
|
// When the field has a jsonrpcusage struct tag specified use that
|
||||||
|
// instead of automatically generating it.
|
||||||
|
if tag := structField.Tag.Get("jsonrpcusage"); tag != "" {
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indirect the pointer if needed.
|
||||||
|
fieldType := structField.Type
|
||||||
|
if fieldType.Kind() == reflect.Ptr {
|
||||||
|
fieldType = fieldType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// When there is a default value, it must also be a pointer due to the
|
||||||
|
// rules enforced by RegisterCmd.
|
||||||
|
if defaultVal != nil {
|
||||||
|
indirect := defaultVal.Elem()
|
||||||
|
defaultVal = &indirect
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle certain types uniquely to provide nicer usage.
|
||||||
|
fieldName := strings.ToLower(structField.Name)
|
||||||
|
switch fieldType.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if defaultVal != nil {
|
||||||
|
return fmt.Sprintf("%s=%q", fieldName,
|
||||||
|
defaultVal.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%q", fieldName)
|
||||||
|
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
return subArrayUsage(fieldType, fieldName)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
return subStructUsage(fieldType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simply return the field name when none of the above special cases
|
||||||
|
// apply.
|
||||||
|
if defaultVal != nil {
|
||||||
|
return fmt.Sprintf("%s=%v", fieldName, defaultVal.Interface())
|
||||||
|
}
|
||||||
|
return fieldName
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodUsageText returns a one-line usage string for the provided command and
|
||||||
|
// method info. This is the main work horse for the exported MethodUsageText
|
||||||
|
// function.
|
||||||
|
func methodUsageText(rtp reflect.Type, defaults map[int]reflect.Value, method string) string {
|
||||||
|
// Generate the individual usage for each field in the command. Several
|
||||||
|
// simplifying assumptions are made here because the RegisterCmd
|
||||||
|
// function has already rigorously enforced the layout.
|
||||||
|
rt := rtp.Elem()
|
||||||
|
numFields := rt.NumField()
|
||||||
|
reqFieldUsages := make([]string, 0, numFields)
|
||||||
|
optFieldUsages := make([]string, 0, numFields)
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
rtf := rt.Field(i)
|
||||||
|
var isOptional bool
|
||||||
|
if kind := rtf.Type.Kind(); kind == reflect.Ptr {
|
||||||
|
isOptional = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultVal *reflect.Value
|
||||||
|
if defVal, ok := defaults[i]; ok {
|
||||||
|
defaultVal = &defVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add human-readable usage to the appropriate slice that is
|
||||||
|
// later used to generate the one-line usage.
|
||||||
|
usage := fieldUsage(rtf, defaultVal)
|
||||||
|
if isOptional {
|
||||||
|
optFieldUsages = append(optFieldUsages, usage)
|
||||||
|
} else {
|
||||||
|
reqFieldUsages = append(reqFieldUsages, usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and return the one-line usage string.
|
||||||
|
usageStr := method
|
||||||
|
if len(reqFieldUsages) > 0 {
|
||||||
|
usageStr += " " + strings.Join(reqFieldUsages, " ")
|
||||||
|
}
|
||||||
|
if len(optFieldUsages) > 0 {
|
||||||
|
usageStr += fmt.Sprintf(" (%s)", strings.Join(optFieldUsages, " "))
|
||||||
|
}
|
||||||
|
return usageStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodUsageText returns a one-line usage string for the provided method. The
|
||||||
|
// provided method must be associated with a registered type. All commands
|
||||||
|
// provided by this package are registered by default.
|
||||||
|
func MethodUsageText(method string) (string, error) {
|
||||||
|
// Look up details about the provided method and error out if not
|
||||||
|
// registered.
|
||||||
|
registerLock.RLock()
|
||||||
|
rtp, ok := methodToConcreteType[method]
|
||||||
|
info := methodToInfo[method]
|
||||||
|
registerLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
str := fmt.Sprintf("%q is not registered", method)
|
||||||
|
return "", makeError(ErrUnregisteredMethod, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the usage for this method has already been generated, simply
|
||||||
|
// return it.
|
||||||
|
if info.usage != "" {
|
||||||
|
return info.usage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and store the usage string for future calls and return it.
|
||||||
|
usage := methodUsageText(rtp, info.defaults, method)
|
||||||
|
registerLock.Lock()
|
||||||
|
info.usage = usage
|
||||||
|
methodToInfo[method] = info
|
||||||
|
registerLock.Unlock()
|
||||||
|
return usage, nil
|
||||||
|
}
|
430
btcjson/v2/btcjson/cmdinfo_test.go
Normal file
430
btcjson/v2/btcjson/cmdinfo_test.go
Normal file
|
@ -0,0 +1,430 @@
|
||||||
|
// Copyright (c) 2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestCmdMethod tests the CmdMethod function to ensure it retuns the expected
|
||||||
|
// methods and errors.
|
||||||
|
func TestCmdMethod(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cmd interface{}
|
||||||
|
method string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unregistered type",
|
||||||
|
cmd: (*int)(nil),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil pointer of registered type",
|
||||||
|
cmd: (*btcjson.GetBlockCmd)(nil),
|
||||||
|
method: "getblock",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil instance of registered type",
|
||||||
|
cmd: &btcjson.GetBlockCountCmd{},
|
||||||
|
method: "getblockcount",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
method, err := btcjson.CmdMethod(test.cmd)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != test.err.(btcjson.Error).ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||||
|
"- got %v (%v), want %v", i, test.name,
|
||||||
|
gotErrorCode, err,
|
||||||
|
test.err.(btcjson.Error).ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure method matches the expected value.
|
||||||
|
if method != test.method {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched method - got %v, "+
|
||||||
|
"want %v", i, test.name, method, test.method)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMethodUsageFlags tests the MethodUsage function ensure it returns the
|
||||||
|
// expected flags and errors.
|
||||||
|
func TestMethodUsageFlags(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
err error
|
||||||
|
flags btcjson.UsageFlag
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unregistered type",
|
||||||
|
method: "bogusmethod",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblock",
|
||||||
|
method: "getblock",
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "walletpassphrase",
|
||||||
|
method: "walletpassphrase",
|
||||||
|
flags: btcjson.UFWalletOnly,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
flags, err := btcjson.MethodUsageFlags(test.method)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != test.err.(btcjson.Error).ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||||
|
"- got %v (%v), want %v", i, test.name,
|
||||||
|
gotErrorCode, err,
|
||||||
|
test.err.(btcjson.Error).ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure flags match the expected value.
|
||||||
|
if flags != test.flags {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched flags - got %v, "+
|
||||||
|
"want %v", i, test.name, flags, test.flags)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMethodUsageText tests the MethodUsageText function ensure it returns the
|
||||||
|
// expected text.
|
||||||
|
func TestMethodUsageText(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
err error
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unregistered type",
|
||||||
|
method: "bogusmethod",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblockcount",
|
||||||
|
method: "getblockcount",
|
||||||
|
expected: "getblockcount",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getblock",
|
||||||
|
method: "getblock",
|
||||||
|
expected: `getblock "hash" (verbose=true verbosetx=false)`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
usage, err := btcjson.MethodUsageText(test.method)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != test.err.(btcjson.Error).ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||||
|
"- got %v (%v), want %v", i, test.name,
|
||||||
|
gotErrorCode, err,
|
||||||
|
test.err.(btcjson.Error).ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure usage matches the expected value.
|
||||||
|
if usage != test.expected {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
||||||
|
"want %v", i, test.name, usage, test.expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the usage again to excerise caching.
|
||||||
|
usage, err = btcjson.MethodUsageText(test.method)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure usage still matches the expected value.
|
||||||
|
if usage != test.expected {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
||||||
|
"want %v", i, test.name, usage, test.expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFieldUsage tests the internal fieldUsage function ensure it returns the
|
||||||
|
// expected text.
|
||||||
|
func TestFieldUsage(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
field reflect.StructField
|
||||||
|
defValue *reflect.Value
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "jsonrpcusage tag override",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s struct {
|
||||||
|
Test int `jsonrpcusage:"testvalue"`
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: "testvalue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "generic interface",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s struct {
|
||||||
|
Test interface{}
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `test`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string without default value",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s struct {
|
||||||
|
Test string
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `"test"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string with default value",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s struct {
|
||||||
|
Test string
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: func() *reflect.Value {
|
||||||
|
value := "default"
|
||||||
|
rv := reflect.ValueOf(&value)
|
||||||
|
return &rv
|
||||||
|
}(),
|
||||||
|
expected: `test="default"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array of strings",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s struct {
|
||||||
|
Test []string
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `["test",...]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array of strings with plural field name 1",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s struct {
|
||||||
|
Keys []string
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `["key",...]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array of strings with plural field name 2",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s struct {
|
||||||
|
Addresses []string
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `["address",...]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array of strings with plural field name 3",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s struct {
|
||||||
|
Capabilities []string
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `["capability",...]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array of structs",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s2 struct {
|
||||||
|
Txid string
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
Capabilities []s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `[{"txid":"value"},...]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array of ints",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s struct {
|
||||||
|
Test []int
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `[test,...]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sub struct with jsonrpcusage tag override",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s2 struct {
|
||||||
|
Test string `jsonrpcusage:"testusage"`
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
Test s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `{testusage}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sub struct with string",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s2 struct {
|
||||||
|
Txid string
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
Test s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `{"txid":"value"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sub struct with int",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s2 struct {
|
||||||
|
Vout int
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
Test s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `{"vout":n}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sub struct with float",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s2 struct {
|
||||||
|
Amount float64
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
Test s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `{"amount":n.nnn}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sub struct with sub struct",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s3 struct {
|
||||||
|
Amount float64
|
||||||
|
}
|
||||||
|
type s2 struct {
|
||||||
|
Template s3
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
Test s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `{"template":{"amount":n.nnn}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sub struct with slice",
|
||||||
|
field: func() reflect.StructField {
|
||||||
|
type s2 struct {
|
||||||
|
Capabilities []string
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
Test s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||||
|
}(),
|
||||||
|
defValue: nil,
|
||||||
|
expected: `{"capabilities":["capability",...]}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Ensure usage matches the expected value.
|
||||||
|
usage := btcjson.TstFieldUsage(test.field, test.defValue)
|
||||||
|
if usage != test.expected {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
||||||
|
"want %v", i, test.name, usage, test.expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
550
btcjson/v2/btcjson/cmdparse.go
Normal file
550
btcjson/v2/btcjson/cmdparse.go
Normal file
|
@ -0,0 +1,550 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// makeParams creates a slice of interface values for the given struct.
|
||||||
|
func makeParams(rt reflect.Type, rv reflect.Value) []interface{} {
|
||||||
|
numFields := rt.NumField()
|
||||||
|
params := make([]interface{}, 0, numFields)
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
rtf := rt.Field(i)
|
||||||
|
rvf := rv.Field(i)
|
||||||
|
if rtf.Type.Kind() == reflect.Ptr {
|
||||||
|
if rvf.IsNil() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rvf.Elem()
|
||||||
|
}
|
||||||
|
params = append(params, rvf.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalCmd marshals the passed command to a JSON-RPC request byte slice that
|
||||||
|
// is suitable for transmission to an RPC server. The provided command type
|
||||||
|
// must be a registered type. All commands provided by this package are
|
||||||
|
// registered by default.
|
||||||
|
func MarshalCmd(id interface{}, cmd interface{}) ([]byte, error) {
|
||||||
|
// Look up the cmd type and error out if not registered.
|
||||||
|
rt := reflect.TypeOf(cmd)
|
||||||
|
registerLock.RLock()
|
||||||
|
method, ok := concreteTypeToMethod[rt]
|
||||||
|
registerLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
str := fmt.Sprintf("%q is not registered", method)
|
||||||
|
return nil, makeError(ErrUnregisteredMethod, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The provided command must not be nil.
|
||||||
|
rv := reflect.ValueOf(cmd)
|
||||||
|
if rv.IsNil() {
|
||||||
|
str := fmt.Sprint("the specified command is nil")
|
||||||
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a slice of interface values in the order of the struct fields
|
||||||
|
// while respecting pointer fields as optional params and only adding
|
||||||
|
// them if they are non-nil.
|
||||||
|
params := makeParams(rt.Elem(), rv.Elem())
|
||||||
|
|
||||||
|
// Generate and marshal the final JSON-RPC request.
|
||||||
|
rawCmd, err := NewRequest(id, method, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(rawCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkNumParams ensures the supplied number of params is at least the minimum
|
||||||
|
// required number for the command and less than the maximum allowed.
|
||||||
|
func checkNumParams(numParams int, info *methodInfo) error {
|
||||||
|
if numParams < info.numReqParams || numParams > info.maxParams {
|
||||||
|
if info.numReqParams == info.maxParams {
|
||||||
|
str := fmt.Sprintf("wrong number of params (expected "+
|
||||||
|
"%d, received %d)", info.numReqParams,
|
||||||
|
numParams)
|
||||||
|
return makeError(ErrNumParams, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
str := fmt.Sprintf("wrong number of params (expected "+
|
||||||
|
"between %d and %d, received %d)", info.numReqParams,
|
||||||
|
info.maxParams, numParams)
|
||||||
|
return makeError(ErrNumParams, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// populateDefaults populates default values into any remaining optional struct
|
||||||
|
// fields that did not have parameters explicitly provided. The caller should
|
||||||
|
// have previously checked that the number of parameters being passed is at
|
||||||
|
// least the required number of parameters to avoid unnecessary work in this
|
||||||
|
// function, but since required fields never have default values, it will work
|
||||||
|
// properly even without the check.
|
||||||
|
func populateDefaults(numParams int, info *methodInfo, rv reflect.Value) {
|
||||||
|
// When there are no more parameters left in the supplied parameters,
|
||||||
|
// any remaining struct fields must be optional. Thus, populate them
|
||||||
|
// with their associated default value as needed.
|
||||||
|
for i := numParams; i < info.maxParams; i++ {
|
||||||
|
rvf := rv.Field(i)
|
||||||
|
if defaultVal, ok := info.defaults[i]; ok {
|
||||||
|
rvf.Set(defaultVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalCmd unmarshals a JSON-RPC request into a suitable concrete command
|
||||||
|
// so long as the method type contained within the marshalled request is
|
||||||
|
// registered.
|
||||||
|
func UnmarshalCmd(r *Request) (interface{}, error) {
|
||||||
|
registerLock.RLock()
|
||||||
|
rtp, ok := methodToConcreteType[r.Method]
|
||||||
|
info := methodToInfo[r.Method]
|
||||||
|
registerLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
str := fmt.Sprintf("%q is not registered", r.Method)
|
||||||
|
return nil, makeError(ErrUnregisteredMethod, str)
|
||||||
|
}
|
||||||
|
rt := rtp.Elem()
|
||||||
|
rvp := reflect.New(rt)
|
||||||
|
rv := rvp.Elem()
|
||||||
|
|
||||||
|
// Ensure the number of parameters are correct.
|
||||||
|
numParams := len(r.Params)
|
||||||
|
if err := checkNumParams(numParams, &info); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through each of the struct fields and unmarshal the associated
|
||||||
|
// parameter into them.
|
||||||
|
for i := 0; i < numParams; i++ {
|
||||||
|
rvf := rv.Field(i)
|
||||||
|
// Unmarshal the parameter into the struct field.
|
||||||
|
concreteVal := rvf.Addr().Interface()
|
||||||
|
if err := json.Unmarshal(r.Params[i], &concreteVal); err != nil {
|
||||||
|
// The most common error is the wrong type, so
|
||||||
|
// explicitly detect that error and make it nicer.
|
||||||
|
fieldName := strings.ToLower(rt.Field(i).Name)
|
||||||
|
if jerr, ok := err.(*json.UnmarshalTypeError); ok {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must "+
|
||||||
|
"be type %v (got %v)", i+1, fieldName,
|
||||||
|
jerr.Type, jerr.Value)
|
||||||
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to showing the underlying error.
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' failed to "+
|
||||||
|
"unmarshal: %v", i+1, fieldName, err)
|
||||||
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When there are less supplied parameters than the total number of
|
||||||
|
// params, any remaining struct fields must be optional. Thus, populate
|
||||||
|
// them with their associated default value as needed.
|
||||||
|
if numParams < info.maxParams {
|
||||||
|
populateDefaults(numParams, &info, rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rvp.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isNumeric returns whether the passed reflect kind is a signed or unsigned
|
||||||
|
// integer of any magnitude or a float of any magnitude.
|
||||||
|
func isNumeric(kind reflect.Kind) bool {
|
||||||
|
switch kind {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||||
|
reflect.Uint64, reflect.Float32, reflect.Float64:
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// typesMaybeCompatible returns whether the source type can possibly be
|
||||||
|
// assigned to the destination type. This is intended as a relatively quick
|
||||||
|
// check to weed out obviously invalid conversions.
|
||||||
|
func typesMaybeCompatible(dest reflect.Type, src reflect.Type) bool {
|
||||||
|
// The same types are obviously compatible.
|
||||||
|
if dest == src {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// When both types are numeric, they are potentially compatibile.
|
||||||
|
srcKind := src.Kind()
|
||||||
|
destKind := dest.Kind()
|
||||||
|
if isNumeric(destKind) && isNumeric(srcKind) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcKind == reflect.String {
|
||||||
|
// Strings can potentially be converted to numeric types.
|
||||||
|
if isNumeric(destKind) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch destKind {
|
||||||
|
// Strings can potentially be converted to bools by
|
||||||
|
// strconv.ParseBool.
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
|
||||||
|
// Strings can be converted to any other type which has as
|
||||||
|
// underlying type of string.
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
|
||||||
|
// Strings can potentially be converted to arrays, slice,
|
||||||
|
// structs, and maps via json.Unmarshal.
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseType returns the type of the argument after indirecting through all
|
||||||
|
// pointers along with how many indirections were necessary.
|
||||||
|
func baseType(arg reflect.Type) (reflect.Type, int) {
|
||||||
|
var numIndirects int
|
||||||
|
for arg.Kind() == reflect.Ptr {
|
||||||
|
arg = arg.Elem()
|
||||||
|
numIndirects++
|
||||||
|
}
|
||||||
|
return arg, numIndirects
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignField is the main workhorse for the NewCmd function which handles
|
||||||
|
// assigning the provided source value to the destination field. It supports
|
||||||
|
// direct type assignments, indirection, conversion of numeric types, and
|
||||||
|
// unmarshaling of strings into arrays, slices, structs, and maps via
|
||||||
|
// json.Unmarshal.
|
||||||
|
func assignField(paramNum int, fieldName string, dest reflect.Value, src reflect.Value) error {
|
||||||
|
// Just error now when the types have no chance of being compatible.
|
||||||
|
destBaseType, destIndirects := baseType(dest.Type())
|
||||||
|
srcBaseType, srcIndirects := baseType(src.Type())
|
||||||
|
if !typesMaybeCompatible(destBaseType, srcBaseType) {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must be type %v (got "+
|
||||||
|
"%v)", paramNum, fieldName, destBaseType, srcBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's possible to simply set the dest to the provided source.
|
||||||
|
// This is the case when the base types are the same or they are both
|
||||||
|
// pointers that can be indirected to be the same without needing to
|
||||||
|
// create pointers for the destination field.
|
||||||
|
if destBaseType == srcBaseType && srcIndirects >= destIndirects {
|
||||||
|
for i := 0; i < srcIndirects-destIndirects; i++ {
|
||||||
|
src = src.Elem()
|
||||||
|
}
|
||||||
|
dest.Set(src)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the destination has more indirects than the source, the extra
|
||||||
|
// pointers have to be created. Only create enough pointers to reach
|
||||||
|
// the same level of indirection as the source so the dest can simply be
|
||||||
|
// set to the provided source when the types are the same.
|
||||||
|
destIndirectsRemaining := destIndirects
|
||||||
|
if destIndirects > srcIndirects {
|
||||||
|
indirectDiff := destIndirects - srcIndirects
|
||||||
|
for i := 0; i < indirectDiff; i++ {
|
||||||
|
dest.Set(reflect.New(dest.Type().Elem()))
|
||||||
|
dest = dest.Elem()
|
||||||
|
destIndirectsRemaining--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if destBaseType == srcBaseType {
|
||||||
|
dest.Set(src)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make any remaining pointers needed to get to the base dest type since
|
||||||
|
// the above direct assign was not possible and conversions are done
|
||||||
|
// against the base types.
|
||||||
|
for i := 0; i < destIndirectsRemaining; i++ {
|
||||||
|
dest.Set(reflect.New(dest.Type().Elem()))
|
||||||
|
dest = dest.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indirect through to the base source value.
|
||||||
|
for src.Kind() == reflect.Ptr {
|
||||||
|
src = src.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform supported type conversions.
|
||||||
|
switch src.Kind() {
|
||||||
|
// Source value is a signed integer of various magnitude.
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
|
||||||
|
switch dest.Kind() {
|
||||||
|
// Destination is a signed integer of various magnitude.
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
|
||||||
|
srcInt := src.Int()
|
||||||
|
if dest.OverflowInt(srcInt) {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' "+
|
||||||
|
"overflows destination type %v",
|
||||||
|
paramNum, fieldName, destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
dest.SetInt(srcInt)
|
||||||
|
|
||||||
|
// Destination is an unsigned integer of various magnitude.
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
|
||||||
|
srcInt := src.Int()
|
||||||
|
if srcInt < 0 || dest.OverflowUint(uint64(srcInt)) {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' "+
|
||||||
|
"overflows destination type %v",
|
||||||
|
paramNum, fieldName, destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
dest.SetUint(uint64(srcInt))
|
||||||
|
|
||||||
|
default:
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must be type "+
|
||||||
|
"%v (got %v)", paramNum, fieldName, destBaseType,
|
||||||
|
srcBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source value is an unsigned integer of various magnitude.
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
|
||||||
|
switch dest.Kind() {
|
||||||
|
// Destination is a signed integer of various magnitude.
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
|
||||||
|
srcUint := src.Uint()
|
||||||
|
if srcUint > uint64(1<<63)-1 {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' "+
|
||||||
|
"overflows destination type %v",
|
||||||
|
paramNum, fieldName, destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
if dest.OverflowInt(int64(srcUint)) {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' "+
|
||||||
|
"overflows destination type %v",
|
||||||
|
paramNum, fieldName, destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
dest.SetInt(int64(srcUint))
|
||||||
|
|
||||||
|
// Destination is an unsigned integer of various magnitude.
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
|
||||||
|
srcUint := src.Uint()
|
||||||
|
if dest.OverflowUint(srcUint) {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' "+
|
||||||
|
"overflows destination type %v",
|
||||||
|
paramNum, fieldName, destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
dest.SetUint(srcUint)
|
||||||
|
|
||||||
|
default:
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must be type "+
|
||||||
|
"%v (got %v)", paramNum, fieldName, destBaseType,
|
||||||
|
srcBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source value is a float.
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
destKind := dest.Kind()
|
||||||
|
if destKind != reflect.Float32 && destKind != reflect.Float64 {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must be type "+
|
||||||
|
"%v (got %v)", paramNum, fieldName, destBaseType,
|
||||||
|
srcBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcFloat := src.Float()
|
||||||
|
if dest.OverflowFloat(srcFloat) {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' overflows "+
|
||||||
|
"destination type %v", paramNum, fieldName,
|
||||||
|
destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
dest.SetFloat(srcFloat)
|
||||||
|
|
||||||
|
// Source value is a string.
|
||||||
|
case reflect.String:
|
||||||
|
switch dest.Kind() {
|
||||||
|
// String -> bool
|
||||||
|
case reflect.Bool:
|
||||||
|
b, err := strconv.ParseBool(src.String())
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must "+
|
||||||
|
"parse to a %v", paramNum, fieldName,
|
||||||
|
destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
dest.SetBool(b)
|
||||||
|
|
||||||
|
// String -> signed integer of varying size.
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
|
||||||
|
srcInt, err := strconv.ParseInt(src.String(), 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must "+
|
||||||
|
"parse to a %v", paramNum, fieldName,
|
||||||
|
destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
if dest.OverflowInt(srcInt) {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' "+
|
||||||
|
"overflows destination type %v",
|
||||||
|
paramNum, fieldName, destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
dest.SetInt(srcInt)
|
||||||
|
|
||||||
|
// String -> unsigned integer of varying size.
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||||
|
reflect.Uint32, reflect.Uint64:
|
||||||
|
|
||||||
|
srcUint, err := strconv.ParseUint(src.String(), 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must "+
|
||||||
|
"parse to a %v", paramNum, fieldName,
|
||||||
|
destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
if dest.OverflowUint(srcUint) {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' "+
|
||||||
|
"overflows destination type %v",
|
||||||
|
paramNum, fieldName, destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
dest.SetUint(srcUint)
|
||||||
|
|
||||||
|
// String -> float of varying size.
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
srcFloat, err := strconv.ParseFloat(src.String(), 0)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must "+
|
||||||
|
"parse to a %v", paramNum, fieldName,
|
||||||
|
destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
if dest.OverflowFloat(srcFloat) {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' "+
|
||||||
|
"overflows destination type %v",
|
||||||
|
paramNum, fieldName, destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
dest.SetFloat(srcFloat)
|
||||||
|
|
||||||
|
// String -> string (typecast).
|
||||||
|
case reflect.String:
|
||||||
|
dest.SetString(src.String())
|
||||||
|
|
||||||
|
// String -> arrays, slices, structs, and maps via
|
||||||
|
// json.Unmarshal.
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map:
|
||||||
|
concreteVal := dest.Addr().Interface()
|
||||||
|
err := json.Unmarshal([]byte(src.String()), &concreteVal)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("parameter #%d '%s' must "+
|
||||||
|
"be valid JSON which unsmarshals to a %v",
|
||||||
|
paramNum, fieldName, destBaseType)
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
dest.Set(reflect.ValueOf(concreteVal).Elem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCmd provides a generic mechanism to create a new command that can marshal
|
||||||
|
// to a JSON-RPC request while respecting the requirements of the provided
|
||||||
|
// method. The method must have been registered with the package already along
|
||||||
|
// with its type definition. All methods associated with the commands exported
|
||||||
|
// by this package are already registered by default.
|
||||||
|
//
|
||||||
|
// The arguments are most efficient when they are the exact same type as the
|
||||||
|
// underlying field in the command struct associated with the the method,
|
||||||
|
// however this function also will perform a variety of conversions to make it
|
||||||
|
// more flexible. This allows, for example, command line args which are strings
|
||||||
|
// to be passed unaltered. In particular, the following conversions are
|
||||||
|
// supported:
|
||||||
|
//
|
||||||
|
// - Conversion between any size signed or unsigned integer so long as the value
|
||||||
|
// does not overflow the destination type
|
||||||
|
// - Conversion between float32 and float64 so long as the value does not
|
||||||
|
// overflow the destination type
|
||||||
|
// - Conversion from string to boolean for everything strconv.ParseBool
|
||||||
|
// recognizes
|
||||||
|
// - Conversion from string to any size integer for everything strconv.ParseInt
|
||||||
|
// and strconv.ParseUint recognizes
|
||||||
|
// - Conversion from string to any size float for everything strconv.ParseFloat
|
||||||
|
// recognizes
|
||||||
|
// - Conversion from string to arrays, slices, structs, and maps by treating
|
||||||
|
// the string as marshalled JSON and calling json.Unmarshal into the
|
||||||
|
// destination field
|
||||||
|
func NewCmd(method string, args ...interface{}) (interface{}, error) {
|
||||||
|
// Look up details about the provided method. Any methods that aren't
|
||||||
|
// registered are an error.
|
||||||
|
registerLock.RLock()
|
||||||
|
rtp, ok := methodToConcreteType[method]
|
||||||
|
info := methodToInfo[method]
|
||||||
|
registerLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
str := fmt.Sprintf("%q is not registered", method)
|
||||||
|
return nil, makeError(ErrUnregisteredMethod, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the number of parameters are correct.
|
||||||
|
numParams := len(args)
|
||||||
|
if err := checkNumParams(numParams, &info); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the appropriate command type for the method. Since all types
|
||||||
|
// are enforced to be a pointer to a struct at registration time, it's
|
||||||
|
// safe to indirect to the struct now.
|
||||||
|
rvp := reflect.New(rtp.Elem())
|
||||||
|
rv := rvp.Elem()
|
||||||
|
rt := rtp.Elem()
|
||||||
|
|
||||||
|
// Loop through each of the struct fields and assign the associated
|
||||||
|
// parameter into them after checking its type validity.
|
||||||
|
for i := 0; i < numParams; i++ {
|
||||||
|
// Attempt to assign each of the arguments to the according
|
||||||
|
// struct field.
|
||||||
|
rvf := rv.Field(i)
|
||||||
|
fieldName := strings.ToLower(rt.Field(i).Name)
|
||||||
|
err := assignField(i+1, fieldName, rvf, reflect.ValueOf(args[i]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rvp.Interface(), nil
|
||||||
|
}
|
519
btcjson/v2/btcjson/cmdparse_test.go
Normal file
519
btcjson/v2/btcjson/cmdparse_test.go
Normal file
|
@ -0,0 +1,519 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAssignField tests the assignField function handles supported combinations
|
||||||
|
// properly.
|
||||||
|
func TestAssignField(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dest interface{}
|
||||||
|
src interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "same types",
|
||||||
|
dest: int8(0),
|
||||||
|
src: int8(100),
|
||||||
|
expected: int8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same types - more source pointers",
|
||||||
|
dest: int8(0),
|
||||||
|
src: func() interface{} {
|
||||||
|
i := int8(100)
|
||||||
|
return &i
|
||||||
|
}(),
|
||||||
|
expected: int8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same types - more dest pointers",
|
||||||
|
dest: func() interface{} {
|
||||||
|
i := int8(0)
|
||||||
|
return &i
|
||||||
|
}(),
|
||||||
|
src: int8(100),
|
||||||
|
expected: int8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - more source pointers",
|
||||||
|
dest: int16(0),
|
||||||
|
src: func() interface{} {
|
||||||
|
i := int8(100)
|
||||||
|
return &i
|
||||||
|
}(),
|
||||||
|
expected: int16(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - both pointers",
|
||||||
|
dest: func() interface{} {
|
||||||
|
i := int8(0)
|
||||||
|
return &i
|
||||||
|
}(),
|
||||||
|
src: func() interface{} {
|
||||||
|
i := int16(100)
|
||||||
|
return &i
|
||||||
|
}(),
|
||||||
|
expected: int8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - int16 -> int8",
|
||||||
|
dest: int8(0),
|
||||||
|
src: int16(100),
|
||||||
|
expected: int8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - int16 -> uint8",
|
||||||
|
dest: uint8(0),
|
||||||
|
src: int16(100),
|
||||||
|
expected: uint8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - uint16 -> int8",
|
||||||
|
dest: int8(0),
|
||||||
|
src: uint16(100),
|
||||||
|
expected: int8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - uint16 -> uint8",
|
||||||
|
dest: uint8(0),
|
||||||
|
src: uint16(100),
|
||||||
|
expected: uint8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - float32 -> float64",
|
||||||
|
dest: float64(0),
|
||||||
|
src: float32(1.5),
|
||||||
|
expected: float64(1.5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - float64 -> float32",
|
||||||
|
dest: float32(0),
|
||||||
|
src: float64(1.5),
|
||||||
|
expected: float32(1.5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - string -> bool",
|
||||||
|
dest: false,
|
||||||
|
src: "true",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - string -> int8",
|
||||||
|
dest: int8(0),
|
||||||
|
src: "100",
|
||||||
|
expected: int8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - string -> uint8",
|
||||||
|
dest: uint8(0),
|
||||||
|
src: "100",
|
||||||
|
expected: uint8(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - string -> float32",
|
||||||
|
dest: float32(0),
|
||||||
|
src: "1.5",
|
||||||
|
expected: float32(1.5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - typecase string -> string",
|
||||||
|
dest: "",
|
||||||
|
src: func() interface{} {
|
||||||
|
type foo string
|
||||||
|
return foo("foo")
|
||||||
|
}(),
|
||||||
|
expected: "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - string -> array",
|
||||||
|
dest: [2]string{},
|
||||||
|
src: `["test","test2"]`,
|
||||||
|
expected: [2]string{"test", "test2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - string -> slice",
|
||||||
|
dest: []string{},
|
||||||
|
src: `["test","test2"]`,
|
||||||
|
expected: []string{"test", "test2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - string -> struct",
|
||||||
|
dest: struct{ A int }{},
|
||||||
|
src: `{"A":100}`,
|
||||||
|
expected: struct{ A int }{100},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convertible types - string -> map",
|
||||||
|
dest: map[string]float64{},
|
||||||
|
src: `{"1Address":1.5}`,
|
||||||
|
expected: map[string]float64{"1Address": 1.5},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
dst := reflect.New(reflect.TypeOf(test.dest)).Elem()
|
||||||
|
src := reflect.ValueOf(test.src)
|
||||||
|
err := btcjson.TstAssignField(1, "testField", dst, src)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inidirect through to the base types to ensure their values
|
||||||
|
// are the same.
|
||||||
|
for dst.Kind() == reflect.Ptr {
|
||||||
|
dst = dst.Elem()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(dst.Interface(), test.expected) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected value - got %v, "+
|
||||||
|
"want %v", i, test.name, dst.Interface(),
|
||||||
|
test.expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAssignFieldErrors tests the assignField function error paths.
|
||||||
|
func TestAssignFieldErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dest interface{}
|
||||||
|
src interface{}
|
||||||
|
err btcjson.Error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "general incompatible int -> string",
|
||||||
|
dest: string(0),
|
||||||
|
src: int(0),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow source int -> dest int",
|
||||||
|
dest: int8(0),
|
||||||
|
src: int(128),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow source int -> dest uint",
|
||||||
|
dest: uint8(0),
|
||||||
|
src: int(256),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int -> float",
|
||||||
|
dest: float32(0),
|
||||||
|
src: int(256),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow source uint64 -> dest int64",
|
||||||
|
dest: int64(0),
|
||||||
|
src: uint64(1 << 63),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow source uint -> dest int",
|
||||||
|
dest: int8(0),
|
||||||
|
src: uint(128),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow source uint -> dest uint",
|
||||||
|
dest: uint8(0),
|
||||||
|
src: uint(256),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint -> float",
|
||||||
|
dest: float32(0),
|
||||||
|
src: uint(256),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float -> int",
|
||||||
|
dest: int(0),
|
||||||
|
src: float32(1.0),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow float64 -> float32",
|
||||||
|
dest: float32(0),
|
||||||
|
src: float64(math.MaxFloat64),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid string -> bool",
|
||||||
|
dest: true,
|
||||||
|
src: "foo",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid string -> int",
|
||||||
|
dest: int8(0),
|
||||||
|
src: "foo",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow string -> int",
|
||||||
|
dest: int8(0),
|
||||||
|
src: "128",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid string -> uint",
|
||||||
|
dest: uint8(0),
|
||||||
|
src: "foo",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow string -> uint",
|
||||||
|
dest: uint8(0),
|
||||||
|
src: "256",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid string -> float",
|
||||||
|
dest: float32(0),
|
||||||
|
src: "foo",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow string -> float",
|
||||||
|
dest: float32(0),
|
||||||
|
src: "1.7976931348623157e+308",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid string -> array",
|
||||||
|
dest: [3]int{},
|
||||||
|
src: "foo",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid string -> slice",
|
||||||
|
dest: []int{},
|
||||||
|
src: "foo",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid string -> struct",
|
||||||
|
dest: struct{ A int }{},
|
||||||
|
src: "foo",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid string -> map",
|
||||||
|
dest: map[string]int{},
|
||||||
|
src: "foo",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
dst := reflect.New(reflect.TypeOf(test.dest)).Elem()
|
||||||
|
src := reflect.ValueOf(test.src)
|
||||||
|
err := btcjson.TstAssignField(1, "testField", dst, src)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != test.err.ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||||
|
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||||
|
err, test.err.ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewCmdErrors ensures the error paths of NewCmd behave as expected.
|
||||||
|
func TestNewCmdErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
args []interface{}
|
||||||
|
err btcjson.Error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unregistered command",
|
||||||
|
method: "boguscommand",
|
||||||
|
args: []interface{}{},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too few parameters to command with required + optional",
|
||||||
|
method: "getblock",
|
||||||
|
args: []interface{}{},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too many parameters to command with no optional",
|
||||||
|
method: "getblockcount",
|
||||||
|
args: []interface{}{"123"},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect parameter type",
|
||||||
|
method: "getblock",
|
||||||
|
args: []interface{}{1},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
_, err := btcjson.NewCmd(test.method, test.args...)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[2]v), "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != test.err.ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||||
|
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||||
|
err, test.err.ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMarshalCmdErrors tests the error paths of the MarshalCmd function.
|
||||||
|
func TestMarshalCmdErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id interface{}
|
||||||
|
cmd interface{}
|
||||||
|
err btcjson.Error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unregistered type",
|
||||||
|
id: 1,
|
||||||
|
cmd: (*int)(nil),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil instance of registered type",
|
||||||
|
id: 1,
|
||||||
|
cmd: (*btcjson.GetBlockCmd)(nil),
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil instance of registered type",
|
||||||
|
id: []int{0, 1},
|
||||||
|
cmd: &btcjson.GetBlockCountCmd{},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
_, err := btcjson.MarshalCmd(test.id, test.cmd)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[2]v), "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != test.err.ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||||
|
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||||
|
err, test.err.ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUnmarshalCmdErrors tests the error paths of the UnmarshalCmd function.
|
||||||
|
func TestUnmarshalCmdErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
request btcjson.Request
|
||||||
|
err btcjson.Error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unregistered type",
|
||||||
|
request: btcjson.Request{
|
||||||
|
Jsonrpc: "1.0",
|
||||||
|
Method: "bogusmethod",
|
||||||
|
Params: nil,
|
||||||
|
ID: nil,
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect number of params",
|
||||||
|
request: btcjson.Request{
|
||||||
|
Jsonrpc: "1.0",
|
||||||
|
Method: "getblockcount",
|
||||||
|
Params: []json.RawMessage{[]byte(`"bogusparam"`)},
|
||||||
|
ID: nil,
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid type for a parameter",
|
||||||
|
request: btcjson.Request{
|
||||||
|
Jsonrpc: "1.0",
|
||||||
|
Method: "getblock",
|
||||||
|
Params: []json.RawMessage{[]byte("1")},
|
||||||
|
ID: nil,
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid JSON for a parameter",
|
||||||
|
request: btcjson.Request{
|
||||||
|
Jsonrpc: "1.0",
|
||||||
|
Method: "getblock",
|
||||||
|
Params: []json.RawMessage{[]byte(`"1`)},
|
||||||
|
ID: nil,
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
_, err := btcjson.UnmarshalCmd(&test.request)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[2]v), "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != test.err.ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||||
|
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||||
|
err, test.err.ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
btcjson/v2/btcjson/error.go
Normal file
111
btcjson/v2/btcjson/error.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorCode identifies a kind of error. These error codes are NOT used for
|
||||||
|
// JSON-RPC response errors.
|
||||||
|
type ErrorCode int
|
||||||
|
|
||||||
|
// These constants are used to identify a specific RuleError.
|
||||||
|
const (
|
||||||
|
// ErrDuplicateMethod indicates a command with the specified method
|
||||||
|
// already exists.
|
||||||
|
ErrDuplicateMethod ErrorCode = iota
|
||||||
|
|
||||||
|
// ErrInvalidUsageFlags indicates one or more unrecognized flag bits
|
||||||
|
// were specified.
|
||||||
|
ErrInvalidUsageFlags
|
||||||
|
|
||||||
|
// ErrInvalidType indicates a type was passed that is not the required
|
||||||
|
// type.
|
||||||
|
ErrInvalidType
|
||||||
|
|
||||||
|
// ErrEmbeddedType indicates the provided command struct contains an
|
||||||
|
// embedded type which is not not supported.
|
||||||
|
ErrEmbeddedType
|
||||||
|
|
||||||
|
// ErrUnexportedField indiciates the provided command struct contains an
|
||||||
|
// unexported field which is not supported.
|
||||||
|
ErrUnexportedField
|
||||||
|
|
||||||
|
// ErrUnsupportedFieldType indicates the type of a field in the provided
|
||||||
|
// command struct is not one of the supported types.
|
||||||
|
ErrUnsupportedFieldType
|
||||||
|
|
||||||
|
// ErrNonOptionalField indicates a non-optional field was specified
|
||||||
|
// after an optional field.
|
||||||
|
ErrNonOptionalField
|
||||||
|
|
||||||
|
// ErrNonOptionalDefault indicates a 'jsonrpcdefault' struct tag was
|
||||||
|
// specified for a non-optional field.
|
||||||
|
ErrNonOptionalDefault
|
||||||
|
|
||||||
|
// ErrMismatchedDefault indicates a 'jsonrpcdefault' struct tag contains
|
||||||
|
// a value that doesn't match the type of the field.
|
||||||
|
ErrMismatchedDefault
|
||||||
|
|
||||||
|
// ErrUnregisteredMethod indicates a method was specified that has not
|
||||||
|
// been registered.
|
||||||
|
ErrUnregisteredMethod
|
||||||
|
|
||||||
|
// ErrMissingDescription indicates a description required to generate
|
||||||
|
// help is missing.
|
||||||
|
ErrMissingDescription
|
||||||
|
|
||||||
|
// ErrNumParams inidcates the number of params supplied do not
|
||||||
|
// match the requirements of the associated command.
|
||||||
|
ErrNumParams
|
||||||
|
|
||||||
|
// numErrorCodes is the maximum error code number used in tests.
|
||||||
|
numErrorCodes
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of ErrorCode values back to their constant names for pretty printing.
|
||||||
|
var errorCodeStrings = map[ErrorCode]string{
|
||||||
|
ErrDuplicateMethod: "ErrDuplicateMethod",
|
||||||
|
ErrInvalidUsageFlags: "ErrInvalidUsageFlags",
|
||||||
|
ErrInvalidType: "ErrInvalidType",
|
||||||
|
ErrEmbeddedType: "ErrEmbeddedType",
|
||||||
|
ErrUnexportedField: "ErrUnexportedField",
|
||||||
|
ErrUnsupportedFieldType: "ErrUnsupportedFieldType",
|
||||||
|
ErrNonOptionalField: "ErrNonOptionalField",
|
||||||
|
ErrNonOptionalDefault: "ErrNonOptionalDefault",
|
||||||
|
ErrMismatchedDefault: "ErrMismatchedDefault",
|
||||||
|
ErrUnregisteredMethod: "ErrUnregisteredMethod",
|
||||||
|
ErrMissingDescription: "ErrMissingDescription",
|
||||||
|
ErrNumParams: "ErrNumParams",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the ErrorCode as a human-readable name.
|
||||||
|
func (e ErrorCode) String() string {
|
||||||
|
if s := errorCodeStrings[e]; s != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error identifies a general error. This differs from an RPCError in that this
|
||||||
|
// error typically is used more by the consumers of the package as opposed to
|
||||||
|
// RPCErrors which are intended to be returned to the client across the wire via
|
||||||
|
// a JSON-RPC Response. The caller can use type assertions to determine the
|
||||||
|
// specific error and access the ErrorCode field.
|
||||||
|
type Error struct {
|
||||||
|
ErrorCode ErrorCode // Describes the kind of error
|
||||||
|
Description string // Human readable description of the issue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error satisfies the error interface and prints human-readable errors.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return e.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeError creates an Error given a set of arguments.
|
||||||
|
func makeError(c ErrorCode, desc string) Error {
|
||||||
|
return Error{ErrorCode: c, Description: desc}
|
||||||
|
}
|
80
btcjson/v2/btcjson/error_test.go
Normal file
80
btcjson/v2/btcjson/error_test.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestErrorCodeStringer tests the stringized output for the ErrorCode type.
|
||||||
|
func TestErrorCodeStringer(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in btcjson.ErrorCode
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{btcjson.ErrDuplicateMethod, "ErrDuplicateMethod"},
|
||||||
|
{btcjson.ErrInvalidUsageFlags, "ErrInvalidUsageFlags"},
|
||||||
|
{btcjson.ErrInvalidType, "ErrInvalidType"},
|
||||||
|
{btcjson.ErrEmbeddedType, "ErrEmbeddedType"},
|
||||||
|
{btcjson.ErrUnexportedField, "ErrUnexportedField"},
|
||||||
|
{btcjson.ErrUnsupportedFieldType, "ErrUnsupportedFieldType"},
|
||||||
|
{btcjson.ErrNonOptionalField, "ErrNonOptionalField"},
|
||||||
|
{btcjson.ErrNonOptionalDefault, "ErrNonOptionalDefault"},
|
||||||
|
{btcjson.ErrMismatchedDefault, "ErrMismatchedDefault"},
|
||||||
|
{btcjson.ErrUnregisteredMethod, "ErrUnregisteredMethod"},
|
||||||
|
{btcjson.ErrNumParams, "ErrNumParams"},
|
||||||
|
{btcjson.ErrMissingDescription, "ErrMissingDescription"},
|
||||||
|
{0xffff, "Unknown ErrorCode (65535)"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect additional error codes that don't have the stringer added.
|
||||||
|
if len(tests)-1 != int(btcjson.TstNumErrorCodes) {
|
||||||
|
t.Errorf("It appears an error code was added without adding an " +
|
||||||
|
"associated stringer test")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.String()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestError tests the error output for the Error type.
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in btcjson.Error
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
btcjson.Error{Description: "some error"},
|
||||||
|
"some error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
btcjson.Error{Description: "human-readable error"},
|
||||||
|
"human-readable error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.Error()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("Error #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
btcjson/v2/btcjson/export_test.go
Normal file
48
btcjson/v2/btcjson/export_test.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
// TstHighestUsageFlagBit makes the internal highestUsageFlagBit parameter
|
||||||
|
// available to the test package.
|
||||||
|
var TstHighestUsageFlagBit = highestUsageFlagBit
|
||||||
|
|
||||||
|
// TstNumErrorCodes makes the internal numErrorCodes parameter available to the
|
||||||
|
// test package.
|
||||||
|
var TstNumErrorCodes = numErrorCodes
|
||||||
|
|
||||||
|
// TstAssignField makes the internal assignField function available to the test
|
||||||
|
// package.
|
||||||
|
var TstAssignField = assignField
|
||||||
|
|
||||||
|
// TstFieldUsage makes the internal fieldUsage function available to the test
|
||||||
|
// package.
|
||||||
|
var TstFieldUsage = fieldUsage
|
||||||
|
|
||||||
|
// TstReflectTypeToJSONType makes the internal reflectTypeToJSONType function
|
||||||
|
// available to the test package.
|
||||||
|
var TstReflectTypeToJSONType = reflectTypeToJSONType
|
||||||
|
|
||||||
|
// TstResultStructHelp makes the internal resultStructHelp function available to
|
||||||
|
// the test package.
|
||||||
|
var TstResultStructHelp = resultStructHelp
|
||||||
|
|
||||||
|
// TstReflectTypeToJSONExample makes the internal reflectTypeToJSONExample
|
||||||
|
// function available to the test package.
|
||||||
|
var TstReflectTypeToJSONExample = reflectTypeToJSONExample
|
||||||
|
|
||||||
|
// TstResultTypeHelp makes the internal resultTypeHelp function available to the
|
||||||
|
// test package.
|
||||||
|
var TstResultTypeHelp = resultTypeHelp
|
||||||
|
|
||||||
|
// TstArgHelp makes the internal argHelp function available to the test package.
|
||||||
|
var TstArgHelp = argHelp
|
||||||
|
|
||||||
|
// TestMethodHelp makes the internal methodHelp function available to the test
|
||||||
|
// package.
|
||||||
|
var TestMethodHelp = methodHelp
|
||||||
|
|
||||||
|
// TstIsValidResultType makes the internal isValidResultType function available
|
||||||
|
// to the test package.
|
||||||
|
var TstIsValidResultType = isValidResultType
|
562
btcjson/v2/btcjson/help.go
Normal file
562
btcjson/v2/btcjson/help.go
Normal file
|
@ -0,0 +1,562 @@
|
||||||
|
// Copyright (c) 2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// baseHelpDescs house the various help labels, types, and example values used
|
||||||
|
// when generating help. The per-command synopsis, field descriptions,
|
||||||
|
// conditions, and result descriptions are to be provided by the caller.
|
||||||
|
var baseHelpDescs = map[string]string{
|
||||||
|
// Misc help labels and output.
|
||||||
|
"help-arguments": "Arguments",
|
||||||
|
"help-arguments-none": "None",
|
||||||
|
"help-result": "Result",
|
||||||
|
"help-result-nothing": "Nothing",
|
||||||
|
"help-default": "default",
|
||||||
|
"help-optional": "optional",
|
||||||
|
"help-required": "required",
|
||||||
|
|
||||||
|
// JSON types.
|
||||||
|
"json-type-numeric": "numeric",
|
||||||
|
"json-type-string": "string",
|
||||||
|
"json-type-bool": "boolean",
|
||||||
|
"json-type-array": "array of ",
|
||||||
|
"json-type-object": "object",
|
||||||
|
"json-type-value": "value",
|
||||||
|
|
||||||
|
// JSON examples.
|
||||||
|
"json-example-string": "value",
|
||||||
|
"json-example-bool": "true|false",
|
||||||
|
"json-example-map-data": "data",
|
||||||
|
"json-example-unknown": "unknown",
|
||||||
|
}
|
||||||
|
|
||||||
|
// descLookupFunc is a function which is used to lookup a description given
|
||||||
|
// a key.
|
||||||
|
type descLookupFunc func(string) string
|
||||||
|
|
||||||
|
// reflectTypeToJSONType returns a string that represents the JSON type
|
||||||
|
// associated with the provided Go type.
|
||||||
|
func reflectTypeToJSONType(xT descLookupFunc, rt reflect.Type) string {
|
||||||
|
kind := rt.Kind()
|
||||||
|
if isNumeric(kind) {
|
||||||
|
return xT("json-type-numeric")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.String:
|
||||||
|
return xT("json-type-string")
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
return xT("json-type-bool")
|
||||||
|
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
return xT("json-type-array") + reflectTypeToJSONType(xT,
|
||||||
|
rt.Elem())
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
return xT("json-type-object")
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
return xT("json-type-object")
|
||||||
|
}
|
||||||
|
|
||||||
|
return xT("json-type-value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// resultStructHelp returns a slice of strings containing the result help output
|
||||||
|
// for a struct. Each line makes use of tabs to separate the relevant pieces so
|
||||||
|
// a tabwriter can be used later to line everything up. The descriptions are
|
||||||
|
// pulled from the active help descriptions map based on the lowercase version
|
||||||
|
// of the provided reflect type and json name (or the lowercase version of the
|
||||||
|
// field name if no json tag was specified).
|
||||||
|
func resultStructHelp(xT descLookupFunc, rt reflect.Type, indentLevel int) []string {
|
||||||
|
indent := strings.Repeat(" ", indentLevel)
|
||||||
|
typeName := strings.ToLower(rt.Name())
|
||||||
|
|
||||||
|
// Generate the help for each of the fields in the result struct.
|
||||||
|
numField := rt.NumField()
|
||||||
|
results := make([]string, 0, numField)
|
||||||
|
for i := 0; i < numField; i++ {
|
||||||
|
rtf := rt.Field(i)
|
||||||
|
|
||||||
|
// The field name to display is the json name when it's
|
||||||
|
// available, otherwise use the lowercase field name.
|
||||||
|
var fieldName string
|
||||||
|
if tag := rtf.Tag.Get("json"); tag != "" {
|
||||||
|
fieldName = strings.Split(tag, ",")[0]
|
||||||
|
} else {
|
||||||
|
fieldName = strings.ToLower(rtf.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deference pointer if needed.
|
||||||
|
rtfType := rtf.Type
|
||||||
|
if rtfType.Kind() == reflect.Ptr {
|
||||||
|
rtfType = rtf.Type.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the JSON example for the result type of this struct
|
||||||
|
// field. When it is a complex type, examine the type and
|
||||||
|
// adjust the opening bracket and brace combination accordingly.
|
||||||
|
fieldType := reflectTypeToJSONType(xT, rtfType)
|
||||||
|
fieldDescKey := typeName + "-" + fieldName
|
||||||
|
fieldExamples, isComplex := reflectTypeToJSONExample(xT,
|
||||||
|
rtfType, indentLevel, fieldDescKey)
|
||||||
|
if isComplex {
|
||||||
|
var brace string
|
||||||
|
kind := rtfType.Kind()
|
||||||
|
if kind == reflect.Array || kind == reflect.Slice {
|
||||||
|
brace = "[{"
|
||||||
|
} else {
|
||||||
|
brace = "{"
|
||||||
|
}
|
||||||
|
result := fmt.Sprintf("%s\"%s\": %s\t(%s)\t%s", indent,
|
||||||
|
fieldName, brace, fieldType, xT(fieldDescKey))
|
||||||
|
results = append(results, result)
|
||||||
|
for _, example := range fieldExamples {
|
||||||
|
results = append(results, example)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result := fmt.Sprintf("%s\"%s\": %s,\t(%s)\t%s", indent,
|
||||||
|
fieldName, fieldExamples[0], fieldType,
|
||||||
|
xT(fieldDescKey))
|
||||||
|
results = append(results, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// reflectTypeToJSONExample generates example usage in the format used by the
|
||||||
|
// help output. It handles arrays, slices and structs recursively. The output
|
||||||
|
// is returned as a slice of lines so the final help can be nicely aligned via
|
||||||
|
// a tab writer. A bool is also returned which specifies whether or not the
|
||||||
|
// type results in a complex JSON object since they need to be handled
|
||||||
|
// differently.
|
||||||
|
func reflectTypeToJSONExample(xT descLookupFunc, rt reflect.Type, indentLevel int, fieldDescKey string) ([]string, bool) {
|
||||||
|
// Indirect pointer if needed.
|
||||||
|
if rt.Kind() == reflect.Ptr {
|
||||||
|
rt = rt.Elem()
|
||||||
|
}
|
||||||
|
kind := rt.Kind()
|
||||||
|
if isNumeric(kind) {
|
||||||
|
if kind == reflect.Float32 || kind == reflect.Float64 {
|
||||||
|
return []string{"n.nnn"}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{"n"}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.String:
|
||||||
|
return []string{`"` + xT("json-example-string") + `"`}, false
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
return []string{xT("json-example-bool")}, false
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
indent := strings.Repeat(" ", indentLevel)
|
||||||
|
results := resultStructHelp(xT, rt, indentLevel+1)
|
||||||
|
|
||||||
|
// An opening brace is needed for the first indent level. For
|
||||||
|
// all others, it will be included as a part of the previous
|
||||||
|
// field.
|
||||||
|
if indentLevel == 0 {
|
||||||
|
newResults := make([]string, len(results)+1)
|
||||||
|
newResults[0] = "{"
|
||||||
|
copy(newResults[1:], results)
|
||||||
|
results = newResults
|
||||||
|
}
|
||||||
|
|
||||||
|
// The closing brace has a comma after it except for the first
|
||||||
|
// indent level. The final tabs are necessary so the tab writer
|
||||||
|
// lines things up properly.
|
||||||
|
closingBrace := indent + "}"
|
||||||
|
if indentLevel > 0 {
|
||||||
|
closingBrace += ","
|
||||||
|
}
|
||||||
|
results = append(results, closingBrace+"\t\t")
|
||||||
|
return results, true
|
||||||
|
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
results, isComplex := reflectTypeToJSONExample(xT, rt.Elem(),
|
||||||
|
indentLevel, fieldDescKey)
|
||||||
|
|
||||||
|
// When the result is complex, it is because this is an array of
|
||||||
|
// objects.
|
||||||
|
if isComplex {
|
||||||
|
// When this is at indent level zero, there is no
|
||||||
|
// previous field to house the opening array bracket, so
|
||||||
|
// replace the opening object brace with the array
|
||||||
|
// syntax. Also, replace the final closing object brace
|
||||||
|
// with the variadiac array closing syntax.
|
||||||
|
indent := strings.Repeat(" ", indentLevel)
|
||||||
|
if indentLevel == 0 {
|
||||||
|
results[0] = indent + "[{"
|
||||||
|
results[len(results)-1] = indent + "},...]"
|
||||||
|
return results, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, the indent level is greater than 0, so
|
||||||
|
// the opening array bracket and object brace are
|
||||||
|
// already a part of the previous field. However, the
|
||||||
|
// closing entry is a simple object brace, so replace it
|
||||||
|
// with the variadiac array closing syntax. The final
|
||||||
|
// tabs are necessary so the tab writer lines things up
|
||||||
|
// properly.
|
||||||
|
results[len(results)-1] = indent + "},...],\t\t"
|
||||||
|
return results, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's an array of primitives, so return the formatted text
|
||||||
|
// accordingly.
|
||||||
|
return []string{fmt.Sprintf("[%s,...]", results[0])}, false
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
indent := strings.Repeat(" ", indentLevel)
|
||||||
|
results := make([]string, 0, 3)
|
||||||
|
|
||||||
|
// An opening brace is needed for the first indent level. For
|
||||||
|
// all others, it will be included as a part of the previous
|
||||||
|
// field.
|
||||||
|
if indentLevel == 0 {
|
||||||
|
results = append(results, indent+"{")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps are a bit special in that they need to have the key,
|
||||||
|
// value, and description of the object entry specifically
|
||||||
|
// called out.
|
||||||
|
innerIndent := strings.Repeat(" ", indentLevel+1)
|
||||||
|
result := fmt.Sprintf("%s%q: %s, (%s) %s", innerIndent,
|
||||||
|
xT(fieldDescKey+"--key"), xT(fieldDescKey+"--value"),
|
||||||
|
reflectTypeToJSONType(xT, rt), xT(fieldDescKey+"--desc"))
|
||||||
|
results = append(results, result)
|
||||||
|
results = append(results, innerIndent+"...")
|
||||||
|
|
||||||
|
results = append(results, indent+"}")
|
||||||
|
return results, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{xT("json-example-unknown")}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// resultTypeHelp generates and returns formatted help for the provided result
|
||||||
|
// type.
|
||||||
|
func resultTypeHelp(xT descLookupFunc, rt reflect.Type, fieldDescKey string) string {
|
||||||
|
// Generate the JSON example for the result type.
|
||||||
|
results, isComplex := reflectTypeToJSONExample(xT, rt, 0, fieldDescKey)
|
||||||
|
|
||||||
|
// When this is a primitive type, add the associated JSON type and
|
||||||
|
// result description into the final string, format it accordingly,
|
||||||
|
// and return it.
|
||||||
|
if !isComplex {
|
||||||
|
return fmt.Sprintf("%s (%s) %s", results[0],
|
||||||
|
reflectTypeToJSONType(xT, rt), xT(fieldDescKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, this is a complex type that already has the JSON types
|
||||||
|
// and descriptions in the results. Thus, use a tab writer to nicely
|
||||||
|
// align the help text.
|
||||||
|
var formatted bytes.Buffer
|
||||||
|
w := new(tabwriter.Writer)
|
||||||
|
w.Init(&formatted, 0, 4, 1, ' ', 0)
|
||||||
|
for i, text := range results {
|
||||||
|
if i == len(results)-1 {
|
||||||
|
fmt.Fprintf(w, text)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(w, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
return formatted.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// argTypeHelp returns the type of provided command argument as a string in the
|
||||||
|
// format used by the help output. In particular, it includes the JSON type
|
||||||
|
// (boolean, numeric, string, array, object) along with optional and the default
|
||||||
|
// value if applicable.
|
||||||
|
func argTypeHelp(xT descLookupFunc, structField reflect.StructField, defaultVal *reflect.Value) string {
|
||||||
|
// Indirect the pointer if needed and track if it's an optional field.
|
||||||
|
fieldType := structField.Type
|
||||||
|
var isOptional bool
|
||||||
|
if fieldType.Kind() == reflect.Ptr {
|
||||||
|
fieldType = fieldType.Elem()
|
||||||
|
isOptional = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// When there is a default value, it must also be a pointer due to the
|
||||||
|
// rules enforced by RegisterCmd.
|
||||||
|
if defaultVal != nil {
|
||||||
|
indirect := defaultVal.Elem()
|
||||||
|
defaultVal = &indirect
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the field type to a JSON type.
|
||||||
|
details := make([]string, 0, 3)
|
||||||
|
details = append(details, reflectTypeToJSONType(xT, fieldType))
|
||||||
|
|
||||||
|
// Add optional and default value to the details if needed.
|
||||||
|
if isOptional {
|
||||||
|
details = append(details, xT("help-optional"))
|
||||||
|
|
||||||
|
// Add the default value if there is one. This is only checked
|
||||||
|
// when the field is optional since a non-optional field can't
|
||||||
|
// have a default value.
|
||||||
|
if defaultVal != nil {
|
||||||
|
val := defaultVal.Interface()
|
||||||
|
if defaultVal.Kind() == reflect.String {
|
||||||
|
val = fmt.Sprintf(`"%s"`, val)
|
||||||
|
}
|
||||||
|
str := fmt.Sprintf("%s=%v", xT("help-default"), val)
|
||||||
|
details = append(details, str)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
details = append(details, xT("help-required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(details, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// argHelp generates and returns formatted help for the provided command.
|
||||||
|
func argHelp(xT descLookupFunc, rtp reflect.Type, defaults map[int]reflect.Value, method string) string {
|
||||||
|
// Return now if the command has no arguments.
|
||||||
|
rt := rtp.Elem()
|
||||||
|
numFields := rt.NumField()
|
||||||
|
if numFields == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the help for each argument in the command. Several
|
||||||
|
// simplifying assumptions are made here because the RegisterCmd
|
||||||
|
// function has already rigorously enforced the layout.
|
||||||
|
args := make([]string, 0, numFields)
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
rtf := rt.Field(i)
|
||||||
|
var defaultVal *reflect.Value
|
||||||
|
if defVal, ok := defaults[i]; ok {
|
||||||
|
defaultVal = &defVal
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldName := strings.ToLower(rtf.Name)
|
||||||
|
helpText := fmt.Sprintf("%d.\t%s\t(%s)\t%s", i+1, fieldName,
|
||||||
|
argTypeHelp(xT, rtf, defaultVal),
|
||||||
|
xT(method+"-"+fieldName))
|
||||||
|
args = append(args, helpText)
|
||||||
|
|
||||||
|
// For types which require a JSON object, or an array of JSON
|
||||||
|
// objects, generate the full syntax for the argument.
|
||||||
|
fieldType := rtf.Type
|
||||||
|
if fieldType.Kind() == reflect.Ptr {
|
||||||
|
fieldType = fieldType.Elem()
|
||||||
|
}
|
||||||
|
kind := fieldType.Kind()
|
||||||
|
switch kind {
|
||||||
|
case reflect.Struct:
|
||||||
|
fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName)
|
||||||
|
resultText := resultTypeHelp(xT, fieldType, fieldDescKey)
|
||||||
|
args = append(args, resultText)
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName)
|
||||||
|
resultText := resultTypeHelp(xT, fieldType, fieldDescKey)
|
||||||
|
args = append(args, resultText)
|
||||||
|
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName)
|
||||||
|
if rtf.Type.Elem().Kind() == reflect.Struct {
|
||||||
|
resultText := resultTypeHelp(xT, fieldType,
|
||||||
|
fieldDescKey)
|
||||||
|
args = append(args, resultText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add argument names, types, and descriptions if there are any. Use a
|
||||||
|
// tab writer to nicely align the help text.
|
||||||
|
var formatted bytes.Buffer
|
||||||
|
w := new(tabwriter.Writer)
|
||||||
|
w.Init(&formatted, 0, 4, 1, ' ', 0)
|
||||||
|
for _, text := range args {
|
||||||
|
fmt.Fprintln(w, text)
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
return formatted.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodHelp generates and returns the help output for the provided command
|
||||||
|
// and method info. This is the main work horse for the exported MethodHelp
|
||||||
|
// function.
|
||||||
|
func methodHelp(xT descLookupFunc, rtp reflect.Type, defaults map[int]reflect.Value, method string, resultTypes []interface{}) string {
|
||||||
|
// Start off with the method usage and help synopsis.
|
||||||
|
help := fmt.Sprintf("%s\n\n%s\n", methodUsageText(rtp, defaults, method),
|
||||||
|
xT(method+"--synopsis"))
|
||||||
|
|
||||||
|
// Generate the help for each argument in the command.
|
||||||
|
if argText := argHelp(xT, rtp, defaults, method); argText != "" {
|
||||||
|
help += fmt.Sprintf("\n%s:\n%s", xT("help-arguments"),
|
||||||
|
argText)
|
||||||
|
} else {
|
||||||
|
help += fmt.Sprintf("\n%s:\n%s\n", xT("help-arguments"),
|
||||||
|
xT("help-arguments-none"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the help text for each result type.
|
||||||
|
resultTexts := make([]string, 0, len(resultTypes))
|
||||||
|
for i := range resultTypes {
|
||||||
|
rtp := reflect.TypeOf(resultTypes[i])
|
||||||
|
fieldDescKey := fmt.Sprintf("%s--result%d", method, i)
|
||||||
|
if resultTypes[i] == nil {
|
||||||
|
resultText := xT("help-result-nothing")
|
||||||
|
resultTexts = append(resultTexts, resultText)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resultText := resultTypeHelp(xT, rtp.Elem(), fieldDescKey)
|
||||||
|
resultTexts = append(resultTexts, resultText)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add result types and descriptions. When there is more than one
|
||||||
|
// result type, also add the condition which triggers it.
|
||||||
|
if len(resultTexts) > 1 {
|
||||||
|
for i, resultText := range resultTexts {
|
||||||
|
condKey := fmt.Sprintf("%s--condition%d", method, i)
|
||||||
|
help += fmt.Sprintf("\n%s (%s):\n%s\n",
|
||||||
|
xT("help-result"), xT(condKey), resultText)
|
||||||
|
}
|
||||||
|
} else if len(resultTexts) > 0 {
|
||||||
|
help += fmt.Sprintf("\n%s:\n%s\n", xT("help-result"),
|
||||||
|
resultTexts[0])
|
||||||
|
} else {
|
||||||
|
help += fmt.Sprintf("\n%s:\n%s\n", xT("help-result"),
|
||||||
|
xT("help-result-nothing"))
|
||||||
|
}
|
||||||
|
return help
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidResultType returns whether the passed reflect kind is one of the
|
||||||
|
// acceptable types for results.
|
||||||
|
func isValidResultType(kind reflect.Kind) bool {
|
||||||
|
if isNumeric(kind) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.String, reflect.Struct, reflect.Array, reflect.Slice,
|
||||||
|
reflect.Bool, reflect.Map:
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateHelp generates and returns help output for the provided method and
|
||||||
|
// result types given a map to provide the appropriate keys for the method
|
||||||
|
// synopsis, field descriptions, conditions, and result descriptions. The
|
||||||
|
// method must be associated with a registered type. All commands provided by
|
||||||
|
// this package are registered by default.
|
||||||
|
//
|
||||||
|
// The resultTypes must be pointer-to-types which represent the specific types
|
||||||
|
// of values the command returns. For example, if the command only returns a
|
||||||
|
// boolean value, there should only be a single entry of (*bool)(nil). Note
|
||||||
|
// that each type must be a single pointer to the type. Therefore, it is
|
||||||
|
// recommended to simply pass a nil pointer cast to the appropriate type as
|
||||||
|
// previously shown.
|
||||||
|
//
|
||||||
|
// The provided descriptions map must contain all of the keys or an error will
|
||||||
|
// be returned which includes the missing key, or the final missing key when
|
||||||
|
// there is more than one key missing. The generated help in the case of such
|
||||||
|
// an error will use the key in place of the description.
|
||||||
|
//
|
||||||
|
// The following outlines the required keys:
|
||||||
|
// - "<method>--synopsis" Synopsis for the command
|
||||||
|
// - "<method>-<lowerfieldname>" Description for each command argument
|
||||||
|
// - "<typename>-<lowerfieldname>" Description for each object field
|
||||||
|
// - "<method>--condition<#>" Description for each result condition
|
||||||
|
// - "<method>--result<#>" Description for each primitive result num
|
||||||
|
//
|
||||||
|
// Notice that the "special" keys synopsis, condition<#>, and result<#> are
|
||||||
|
// preceded by a double dash to ensure they don't conflict with field names.
|
||||||
|
//
|
||||||
|
// The condition keys are only required when there is more than on result type,
|
||||||
|
// and the result key for a given result type is only required if it's not an
|
||||||
|
// object.
|
||||||
|
//
|
||||||
|
// For example, consider the 'help' command itself. There are two possible
|
||||||
|
// returns depending on the provided parameters. So, the help would be
|
||||||
|
// generated by calling the function as follows
|
||||||
|
// GenerateHelp("help", descs, (*string)(nil), (*string)(nil)).
|
||||||
|
//
|
||||||
|
// The following keys would then be required in the provided descriptions map:
|
||||||
|
//
|
||||||
|
// - "help--synopsis": "Returns a list of all commands or help for ...."
|
||||||
|
// - "help-command": "The command to retrieve help for",
|
||||||
|
// - "help--condition0": "no command provided"
|
||||||
|
// - "help--condition1": "command specified"
|
||||||
|
// - "help--result0": "List of commands"
|
||||||
|
// - "help--result1": "Help for specified command"
|
||||||
|
func GenerateHelp(method string, descs map[string]string, resultTypes ...interface{}) (string, error) {
|
||||||
|
// Look up details about the provided method and error out if not
|
||||||
|
// registered.
|
||||||
|
registerLock.RLock()
|
||||||
|
rtp, ok := methodToConcreteType[method]
|
||||||
|
info := methodToInfo[method]
|
||||||
|
registerLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
str := fmt.Sprintf("%q is not registered", method)
|
||||||
|
return "", makeError(ErrUnregisteredMethod, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each result type is a pointer to a supported type (or nil).
|
||||||
|
for i, resultType := range resultTypes {
|
||||||
|
if resultType == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rtp := reflect.TypeOf(resultType)
|
||||||
|
if rtp.Kind() != reflect.Ptr {
|
||||||
|
str := fmt.Sprintf("result #%d (%v) is not a pointer",
|
||||||
|
i, rtp.Kind())
|
||||||
|
return "", makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
elemKind := rtp.Elem().Kind()
|
||||||
|
if !isValidResultType(elemKind) {
|
||||||
|
str := fmt.Sprintf("result #%d (%v) is not an allowed "+
|
||||||
|
"type", i, elemKind)
|
||||||
|
return "", makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a closure for the description lookup function which falls back
|
||||||
|
// to the base help descritptions map for unrecognized keys and tracks
|
||||||
|
// and missing keys.
|
||||||
|
var missingKey string
|
||||||
|
xT := func(key string) string {
|
||||||
|
if desc, ok := descs[key]; ok {
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
if desc, ok := baseHelpDescs[key]; ok {
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
|
||||||
|
missingKey = key
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and return the help for the method.
|
||||||
|
help := methodHelp(xT, rtp, info.defaults, method, resultTypes)
|
||||||
|
if missingKey != "" {
|
||||||
|
return help, makeError(ErrMissingDescription, missingKey)
|
||||||
|
}
|
||||||
|
return help, nil
|
||||||
|
}
|
737
btcjson/v2/btcjson/help_test.go
Normal file
737
btcjson/v2/btcjson/help_test.go
Normal file
|
@ -0,0 +1,737 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestHelpReflectInternals ensures the various help functions which deal with
|
||||||
|
// reflect types work as expected for various Go types.
|
||||||
|
func TestHelpReflectInternals(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
reflectType reflect.Type
|
||||||
|
indentLevel int
|
||||||
|
key string
|
||||||
|
examples []string
|
||||||
|
isComplex bool
|
||||||
|
help string
|
||||||
|
isInvalid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "int",
|
||||||
|
reflectType: reflect.TypeOf(int(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "*int",
|
||||||
|
reflectType: reflect.TypeOf((*int)(nil)),
|
||||||
|
key: "json-type-value",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-value) fdk",
|
||||||
|
isInvalid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int8",
|
||||||
|
reflectType: reflect.TypeOf(int8(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int16",
|
||||||
|
reflectType: reflect.TypeOf(int16(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int32",
|
||||||
|
reflectType: reflect.TypeOf(int32(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64",
|
||||||
|
reflectType: reflect.TypeOf(int64(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint",
|
||||||
|
reflectType: reflect.TypeOf(uint(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint8",
|
||||||
|
reflectType: reflect.TypeOf(uint8(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint16",
|
||||||
|
reflectType: reflect.TypeOf(uint16(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint32",
|
||||||
|
reflectType: reflect.TypeOf(uint32(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint64",
|
||||||
|
reflectType: reflect.TypeOf(uint64(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n"},
|
||||||
|
help: "n (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32",
|
||||||
|
reflectType: reflect.TypeOf(float32(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n.nnn"},
|
||||||
|
help: "n.nnn (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64",
|
||||||
|
reflectType: reflect.TypeOf(float64(0)),
|
||||||
|
key: "json-type-numeric",
|
||||||
|
examples: []string{"n.nnn"},
|
||||||
|
help: "n.nnn (json-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string",
|
||||||
|
reflectType: reflect.TypeOf(""),
|
||||||
|
key: "json-type-string",
|
||||||
|
examples: []string{`"json-example-string"`},
|
||||||
|
help: "\"json-example-string\" (json-type-string) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool",
|
||||||
|
reflectType: reflect.TypeOf(true),
|
||||||
|
key: "json-type-bool",
|
||||||
|
examples: []string{"json-example-bool"},
|
||||||
|
help: "json-example-bool (json-type-bool) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array of int",
|
||||||
|
reflectType: reflect.TypeOf([1]int{0}),
|
||||||
|
key: "json-type-arrayjson-type-numeric",
|
||||||
|
examples: []string{"[n,...]"},
|
||||||
|
help: "[n,...] (json-type-arrayjson-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "slice of int",
|
||||||
|
reflectType: reflect.TypeOf([]int{0}),
|
||||||
|
key: "json-type-arrayjson-type-numeric",
|
||||||
|
examples: []string{"[n,...]"},
|
||||||
|
help: "[n,...] (json-type-arrayjson-type-numeric) fdk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct",
|
||||||
|
reflectType: reflect.TypeOf(struct{}{}),
|
||||||
|
key: "json-type-object",
|
||||||
|
examples: []string{"{", "}\t\t"},
|
||||||
|
isComplex: true,
|
||||||
|
help: "{\n} ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct indent level 1",
|
||||||
|
reflectType: reflect.TypeOf(struct{ field int }{}),
|
||||||
|
indentLevel: 1,
|
||||||
|
key: "json-type-object",
|
||||||
|
examples: []string{
|
||||||
|
" \"field\": n,\t(json-type-numeric)\t-field",
|
||||||
|
" },\t\t",
|
||||||
|
},
|
||||||
|
help: "{\n" +
|
||||||
|
" \"field\": n, (json-type-numeric) -field\n" +
|
||||||
|
"} ",
|
||||||
|
isComplex: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array of struct indent level 0",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
field int
|
||||||
|
}
|
||||||
|
return reflect.TypeOf([]s{})
|
||||||
|
}(),
|
||||||
|
key: "json-type-arrayjson-type-object",
|
||||||
|
examples: []string{
|
||||||
|
"[{",
|
||||||
|
" \"field\": n,\t(json-type-numeric)\ts-field",
|
||||||
|
"},...]",
|
||||||
|
},
|
||||||
|
help: "[{\n" +
|
||||||
|
" \"field\": n, (json-type-numeric) s-field\n" +
|
||||||
|
"},...]",
|
||||||
|
isComplex: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "array of struct indent level 1",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
field int
|
||||||
|
}
|
||||||
|
return reflect.TypeOf([]s{})
|
||||||
|
}(),
|
||||||
|
indentLevel: 1,
|
||||||
|
key: "json-type-arrayjson-type-object",
|
||||||
|
examples: []string{
|
||||||
|
" \"field\": n,\t(json-type-numeric)\ts-field",
|
||||||
|
" },...],\t\t",
|
||||||
|
},
|
||||||
|
help: "[{\n" +
|
||||||
|
" \"field\": n, (json-type-numeric) s-field\n" +
|
||||||
|
"},...]",
|
||||||
|
isComplex: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "map",
|
||||||
|
reflectType: reflect.TypeOf(map[string]string{}),
|
||||||
|
key: "json-type-object",
|
||||||
|
examples: []string{"{",
|
||||||
|
" \"fdk--key\": fdk--value, (json-type-object) fdk--desc",
|
||||||
|
" ...", "}",
|
||||||
|
},
|
||||||
|
help: "{\n" +
|
||||||
|
" \"fdk--key\": fdk--value, (json-type-object) fdk--desc\n" +
|
||||||
|
" ...\n" +
|
||||||
|
"}",
|
||||||
|
isComplex: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "complex",
|
||||||
|
reflectType: reflect.TypeOf(complex64(0)),
|
||||||
|
key: "json-type-value",
|
||||||
|
examples: []string{"json-example-unknown"},
|
||||||
|
help: "json-example-unknown (json-type-value) fdk",
|
||||||
|
isInvalid: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xT := func(key string) string {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Ensure the description key is the expected value.
|
||||||
|
key := btcjson.TstReflectTypeToJSONType(xT, test.reflectType)
|
||||||
|
if key != test.key {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected key - got: %v, "+
|
||||||
|
"want: %v", i, test.name, key, test.key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the generated example is as expected.
|
||||||
|
examples, isComplex := btcjson.TstReflectTypeToJSONExample(xT,
|
||||||
|
test.reflectType, test.indentLevel, "fdk")
|
||||||
|
if isComplex != test.isComplex {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected isComplex - got: %v, "+
|
||||||
|
"want: %v", i, test.name, isComplex,
|
||||||
|
test.isComplex)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(examples) != len(test.examples) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected result length - "+
|
||||||
|
"got: %v, want: %v", i, test.name, len(examples),
|
||||||
|
len(test.examples))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for j, example := range examples {
|
||||||
|
if example != test.examples[j] {
|
||||||
|
t.Errorf("Test #%d (%s) example #%d unexpected "+
|
||||||
|
"example - got: %v, want: %v", i,
|
||||||
|
test.name, j, example, test.examples[j])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the generated result type help is as expected.
|
||||||
|
helpText := btcjson.TstResultTypeHelp(xT, test.reflectType, "fdk")
|
||||||
|
if helpText != test.help {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected result help - "+
|
||||||
|
"got: %v, want: %v", i, test.name, helpText,
|
||||||
|
test.help)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid := btcjson.TstIsValidResultType(test.reflectType.Kind())
|
||||||
|
if isValid != !test.isInvalid {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected result type validity "+
|
||||||
|
"- got: %v", i, test.name, isValid)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestResultStructHelp ensures the expected help text format is returned for
|
||||||
|
// various Go struct types.
|
||||||
|
func TestResultStructHelp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
reflectType reflect.Type
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty struct",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct{}
|
||||||
|
return reflect.TypeOf(s{})
|
||||||
|
}(),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct with primitive field",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
field int
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(s{})
|
||||||
|
}(),
|
||||||
|
expected: []string{
|
||||||
|
"\"field\": n,\t(json-type-numeric)\ts-field",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct with primitive field and json tag",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
Field int `json:"f"`
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(s{})
|
||||||
|
}(),
|
||||||
|
expected: []string{
|
||||||
|
"\"f\": n,\t(json-type-numeric)\ts-f",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct with array of primitive field",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
field []int
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(s{})
|
||||||
|
}(),
|
||||||
|
expected: []string{
|
||||||
|
"\"field\": [n,...],\t(json-type-arrayjson-type-numeric)\ts-field",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct with sub-struct field",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s2 struct {
|
||||||
|
subField int
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
field s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(s{})
|
||||||
|
}(),
|
||||||
|
expected: []string{
|
||||||
|
"\"field\": {\t(json-type-object)\ts-field",
|
||||||
|
"{",
|
||||||
|
" \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
|
||||||
|
"}\t\t",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct with sub-struct field pointer",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s2 struct {
|
||||||
|
subField int
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
field *s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(s{})
|
||||||
|
}(),
|
||||||
|
expected: []string{
|
||||||
|
"\"field\": {\t(json-type-object)\ts-field",
|
||||||
|
"{",
|
||||||
|
" \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
|
||||||
|
"}\t\t",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct with array of structs field",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s2 struct {
|
||||||
|
subField int
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
field []s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(s{})
|
||||||
|
}(),
|
||||||
|
expected: []string{
|
||||||
|
"\"field\": [{\t(json-type-arrayjson-type-object)\ts-field",
|
||||||
|
"[{",
|
||||||
|
" \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
|
||||||
|
"},...]",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xT := func(key string) string {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
results := btcjson.TstResultStructHelp(xT, test.reflectType, 0)
|
||||||
|
if len(results) != len(test.expected) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected result length - "+
|
||||||
|
"got: %v, want: %v", i, test.name, len(results),
|
||||||
|
len(test.expected))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for j, result := range results {
|
||||||
|
if result != test.expected[j] {
|
||||||
|
t.Errorf("Test #%d (%s) result #%d unexpected "+
|
||||||
|
"result - got: %v, want: %v", i,
|
||||||
|
test.name, j, result, test.expected[j])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHelpArgInternals ensures the various help functions which deal with
|
||||||
|
// arguments work as expected for various argument types.
|
||||||
|
func TestHelpArgInternals(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
reflectType reflect.Type
|
||||||
|
defaults map[int]reflect.Value
|
||||||
|
help string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "command with no args",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct{}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
defaults: nil,
|
||||||
|
help: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with one required arg",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
Field int
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
defaults: nil,
|
||||||
|
help: "1. field (json-type-numeric, help-required) test-field\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with one optional arg, no default",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
Optional *int
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
defaults: nil,
|
||||||
|
help: "1. optional (json-type-numeric, help-optional) test-optional\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with one optional arg with default",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
Optional *string
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
defaults: func() map[int]reflect.Value {
|
||||||
|
defVal := "test"
|
||||||
|
return map[int]reflect.Value{
|
||||||
|
0: reflect.ValueOf(&defVal),
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
help: "1. optional (json-type-string, help-optional, help-default=\"test\") test-optional\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with struct field",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s2 struct {
|
||||||
|
F int8
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
Field s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
defaults: nil,
|
||||||
|
help: "1. field (json-type-object, help-required) test-field\n" +
|
||||||
|
"{\n" +
|
||||||
|
" \"f\": n, (json-type-numeric) s2-f\n" +
|
||||||
|
"} \n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with map field",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
Field map[string]float64
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
defaults: nil,
|
||||||
|
help: "1. field (json-type-object, help-required) test-field\n" +
|
||||||
|
"{\n" +
|
||||||
|
" \"test-field--key\": test-field--value, (json-type-object) test-field--desc\n" +
|
||||||
|
" ...\n" +
|
||||||
|
"}\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with slice of primitives field",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
Field []int64
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
defaults: nil,
|
||||||
|
help: "1. field (json-type-arrayjson-type-numeric, help-required) test-field\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with slice of structs field",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s2 struct {
|
||||||
|
F int64
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
Field []s2
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
defaults: nil,
|
||||||
|
help: "1. field (json-type-arrayjson-type-object, help-required) test-field\n" +
|
||||||
|
"[{\n" +
|
||||||
|
" \"f\": n, (json-type-numeric) s2-f\n" +
|
||||||
|
"},...]\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xT := func(key string) string {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
help := btcjson.TstArgHelp(xT, test.reflectType, test.defaults,
|
||||||
|
test.method)
|
||||||
|
if help != test.help {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected help - got:\n%v\n"+
|
||||||
|
"want:\n%v", i, test.name, help, test.help)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMethodHelp ensures the method help function works as expected for various
|
||||||
|
// command structs.
|
||||||
|
func TestMethodHelp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
reflectType reflect.Type
|
||||||
|
defaults map[int]reflect.Value
|
||||||
|
resultTypes []interface{}
|
||||||
|
help string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "command with no args or results",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct{}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
help: "test\n\ntest--synopsis\n\n" +
|
||||||
|
"help-arguments:\nhelp-arguments-none\n\n" +
|
||||||
|
"help-result:\nhelp-result-nothing\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with no args and one primitive result",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct{}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
resultTypes: []interface{}{(*int64)(nil)},
|
||||||
|
help: "test\n\ntest--synopsis\n\n" +
|
||||||
|
"help-arguments:\nhelp-arguments-none\n\n" +
|
||||||
|
"help-result:\nn (json-type-numeric) test--result0\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with no args and two results",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct{}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
resultTypes: []interface{}{(*int64)(nil), nil},
|
||||||
|
help: "test\n\ntest--synopsis\n\n" +
|
||||||
|
"help-arguments:\nhelp-arguments-none\n\n" +
|
||||||
|
"help-result (test--condition0):\nn (json-type-numeric) test--result0\n\n" +
|
||||||
|
"help-result (test--condition1):\nhelp-result-nothing\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with primitive arg and no results",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
Field bool
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
help: "test field\n\ntest--synopsis\n\n" +
|
||||||
|
"help-arguments:\n1. field (json-type-bool, help-required) test-field\n\n" +
|
||||||
|
"help-result:\nhelp-result-nothing\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "command with primitive optional and no results",
|
||||||
|
method: "test",
|
||||||
|
reflectType: func() reflect.Type {
|
||||||
|
type s struct {
|
||||||
|
Field *bool
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*s)(nil))
|
||||||
|
}(),
|
||||||
|
help: "test (field)\n\ntest--synopsis\n\n" +
|
||||||
|
"help-arguments:\n1. field (json-type-bool, help-optional) test-field\n\n" +
|
||||||
|
"help-result:\nhelp-result-nothing\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xT := func(key string) string {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
help := btcjson.TestMethodHelp(xT, test.reflectType,
|
||||||
|
test.defaults, test.method, test.resultTypes)
|
||||||
|
if help != test.help {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected help - got:\n%v\n"+
|
||||||
|
"want:\n%v", i, test.name, help, test.help)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGenerateHelpErrors ensures the GenerateHelp function returns the expected
|
||||||
|
// errors.
|
||||||
|
func TestGenerateHelpErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
resultTypes []interface{}
|
||||||
|
err btcjson.Error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unregistered command",
|
||||||
|
method: "boguscommand",
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-pointer result type",
|
||||||
|
method: "help",
|
||||||
|
resultTypes: []interface{}{0},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid result type",
|
||||||
|
method: "help",
|
||||||
|
resultTypes: []interface{}{(*complex64)(nil)},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing description",
|
||||||
|
method: "help",
|
||||||
|
resultTypes: []interface{}{(*string)(nil), nil},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrMissingDescription},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
_, err := btcjson.GenerateHelp(test.method, nil,
|
||||||
|
test.resultTypes...)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T (%[2]v), "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != test.err.ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||||
|
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||||
|
err, test.err.ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGenerateHelp performs a very basic test to ensure GenerateHelp is working
|
||||||
|
// as expected. The internal are testd much more thoroughly in other tests, so
|
||||||
|
// there is no need to add more tests here.
|
||||||
|
func TestGenerateHelp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
descs := map[string]string{
|
||||||
|
"help--synopsis": "test",
|
||||||
|
"help-command": "test",
|
||||||
|
}
|
||||||
|
help, err := btcjson.GenerateHelp("help", descs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GenerateHelp: unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
wantHelp := "help (\"command\")\n\n" +
|
||||||
|
"test\n\nArguments:\n1. command (string, optional) test\n\n" +
|
||||||
|
"Result:\nNothing\n"
|
||||||
|
if help != wantHelp {
|
||||||
|
t.Fatalf("GenerateHelp: unexpected help - got\n%v\nwant\n%v",
|
||||||
|
help, wantHelp)
|
||||||
|
}
|
||||||
|
}
|
69
btcjson/v2/btcjson/helpers.go
Normal file
69
btcjson/v2/btcjson/helpers.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
// Bool is a helper routine that allocates a new bool value to store v and
|
||||||
|
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||||
|
func Bool(v bool) *bool {
|
||||||
|
p := new(bool)
|
||||||
|
*p = v
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int is a helper routine that allocates a new int value to store v and
|
||||||
|
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||||
|
func Int(v int) *int {
|
||||||
|
p := new(int)
|
||||||
|
*p = v
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint is a helper routine that allocates a new uint value to store v and
|
||||||
|
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||||
|
func Uint(v uint) *uint {
|
||||||
|
p := new(uint)
|
||||||
|
*p = v
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 is a helper routine that allocates a new int32 value to store v and
|
||||||
|
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||||
|
func Int32(v int32) *int32 {
|
||||||
|
p := new(int32)
|
||||||
|
*p = v
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 is a helper routine that allocates a new uint32 value to store v and
|
||||||
|
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||||
|
func Uint32(v uint32) *uint32 {
|
||||||
|
p := new(uint32)
|
||||||
|
*p = v
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 is a helper routine that allocates a new int64 value to store v and
|
||||||
|
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||||
|
func Int64(v int64) *int64 {
|
||||||
|
p := new(int64)
|
||||||
|
*p = v
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 is a helper routine that allocates a new uint64 value to store v and
|
||||||
|
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||||
|
func Uint64(v uint64) *uint64 {
|
||||||
|
p := new(uint64)
|
||||||
|
*p = v
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is a helper routine that allocates a new string value to store v and
|
||||||
|
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||||
|
func String(v string) *string {
|
||||||
|
p := new(string)
|
||||||
|
*p = v
|
||||||
|
return p
|
||||||
|
}
|
115
btcjson/v2/btcjson/helpers_test.go
Normal file
115
btcjson/v2/btcjson/helpers_test.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestHelpers tests the various helper functions which create pointers to
|
||||||
|
// primitive types.
|
||||||
|
func TestHelpers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
f func() interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bool",
|
||||||
|
f: func() interface{} {
|
||||||
|
return btcjson.Bool(true)
|
||||||
|
},
|
||||||
|
expected: func() interface{} {
|
||||||
|
val := true
|
||||||
|
return &val
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int",
|
||||||
|
f: func() interface{} {
|
||||||
|
return btcjson.Int(5)
|
||||||
|
},
|
||||||
|
expected: func() interface{} {
|
||||||
|
val := int(5)
|
||||||
|
return &val
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint",
|
||||||
|
f: func() interface{} {
|
||||||
|
return btcjson.Uint(5)
|
||||||
|
},
|
||||||
|
expected: func() interface{} {
|
||||||
|
val := uint(5)
|
||||||
|
return &val
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int32",
|
||||||
|
f: func() interface{} {
|
||||||
|
return btcjson.Int32(5)
|
||||||
|
},
|
||||||
|
expected: func() interface{} {
|
||||||
|
val := int32(5)
|
||||||
|
return &val
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint32",
|
||||||
|
f: func() interface{} {
|
||||||
|
return btcjson.Uint32(5)
|
||||||
|
},
|
||||||
|
expected: func() interface{} {
|
||||||
|
val := uint32(5)
|
||||||
|
return &val
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64",
|
||||||
|
f: func() interface{} {
|
||||||
|
return btcjson.Int64(5)
|
||||||
|
},
|
||||||
|
expected: func() interface{} {
|
||||||
|
val := int64(5)
|
||||||
|
return &val
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint64",
|
||||||
|
f: func() interface{} {
|
||||||
|
return btcjson.Uint64(5)
|
||||||
|
},
|
||||||
|
expected: func() interface{} {
|
||||||
|
val := uint64(5)
|
||||||
|
return &val
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string",
|
||||||
|
f: func() interface{} {
|
||||||
|
return btcjson.String("abc")
|
||||||
|
},
|
||||||
|
expected: func() interface{} {
|
||||||
|
val := "abc"
|
||||||
|
return &val
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.f()
|
||||||
|
if !reflect.DeepEqual(result, test.expected) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected value - got %v, "+
|
||||||
|
"want %v", i, test.name, result, test.expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
btcjson/v2/btcjson/jsonrpc.go
Normal file
150
btcjson/v2/btcjson/jsonrpc.go
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RPCErrorCode represents an error code to be used as a part of an RPCError
|
||||||
|
// which is in turn used in a JSON-RPC Response object.
|
||||||
|
//
|
||||||
|
// A specific type is used to help ensure the wrong errors aren't used.
|
||||||
|
type RPCErrorCode int
|
||||||
|
|
||||||
|
// RPCError represents an error that is used as a part of a JSON-RPC Response
|
||||||
|
// object.
|
||||||
|
type RPCError struct {
|
||||||
|
Code RPCErrorCode `json:"code,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guarantee RPCError satisifies the builtin error interface.
|
||||||
|
var _, _ error = RPCError{}, (*RPCError)(nil)
|
||||||
|
|
||||||
|
// Error returns a string describing the RPC error. This satisifies the
|
||||||
|
// builtin error interface.
|
||||||
|
func (e RPCError) Error() string {
|
||||||
|
return fmt.Sprintf("%d: %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRPCError constructs and returns a new JSON-RPC error that is suitable
|
||||||
|
// for use in a JSON-RPC Response object.
|
||||||
|
func NewRPCError(code RPCErrorCode, message string) *RPCError {
|
||||||
|
return &RPCError{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidIDType checks that the ID field (which can go in any of the JSON-RPC
|
||||||
|
// requests, responses, or notifications) is valid. JSON-RPC 1.0 allows any
|
||||||
|
// valid JSON type. JSON-RPC 2.0 (which bitcoind follows for some parts) only
|
||||||
|
// allows string, number, or null, so this function restricts the allowed types
|
||||||
|
// to that list. This funciton is only provided in case the caller is manually
|
||||||
|
// marshalling for some reason. The functions which accept an ID in this
|
||||||
|
// package already call this function to ensure the provided id is valid.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request is a type for raw JSON-RPC 1.0 requests. The Method field identifies
|
||||||
|
// the specific command type which in turns leads to different parameters.
|
||||||
|
// Callers typically will not use this directly since this package provides a
|
||||||
|
// statically typed command infrastructure which handles creation of these
|
||||||
|
// requests, however this struct it being exported in case the caller wants to
|
||||||
|
// construct raw requests for some reason.
|
||||||
|
type Request struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params []json.RawMessage `json:"params"`
|
||||||
|
ID interface{} `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest returns a new JSON-RPC 1.0 request object given the provided id,
|
||||||
|
// method, and parameters. The parameters are marshalled into a json.RawMessage
|
||||||
|
// for the Params field of the returned request object. This function is only
|
||||||
|
// provided in case the caller wants to construct raw requests for some reason.
|
||||||
|
//
|
||||||
|
// Typically callers will instead want to create a registered concrete command
|
||||||
|
// type with the NewCmd or New<Foo>Cmd functions and call the MarshalCmd
|
||||||
|
// function with that command to generate the marshalled JSON-RPC request.
|
||||||
|
func NewRequest(id interface{}, method string, params []interface{}) (*Request, error) {
|
||||||
|
if !IsValidIDType(id) {
|
||||||
|
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
||||||
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawParams := make([]json.RawMessage, 0, len(params))
|
||||||
|
for _, param := range params {
|
||||||
|
marshalledParam, err := json.Marshal(param)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawMessage := json.RawMessage(marshalledParam)
|
||||||
|
rawParams = append(rawParams, rawMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Request{
|
||||||
|
Jsonrpc: "1.0",
|
||||||
|
ID: id,
|
||||||
|
Method: method,
|
||||||
|
Params: rawParams,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response is the general form of a JSON-RPC response. The type of the Result
|
||||||
|
// field varies from one command to the next, so it is implemented as an
|
||||||
|
// interface. The ID field has to be a pointer for Go to put a null in it when
|
||||||
|
// empty.
|
||||||
|
type Response struct {
|
||||||
|
Result json.RawMessage `json:"result"`
|
||||||
|
Error *RPCError `json:"error"`
|
||||||
|
ID *interface{} `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResponse returns a new JSON-RPC response object given the provided id,
|
||||||
|
// marshalled result, and RPC error. This function is only provided in case the
|
||||||
|
// caller wants to construct raw responses for some reason.
|
||||||
|
//
|
||||||
|
// Typically callers will instead want to create the fully marshalled JSON-RPC
|
||||||
|
// response to send over the wire with the MarshalResponse function.
|
||||||
|
func NewResponse(id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) {
|
||||||
|
if !IsValidIDType(id) {
|
||||||
|
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
||||||
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
pid := &id
|
||||||
|
return &Response{
|
||||||
|
Result: marshalledResult,
|
||||||
|
Error: rpcErr,
|
||||||
|
ID: pid,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalResponse marshals the passed id, result, and RPCError to a JSON-RPC
|
||||||
|
// response byte slice that is suitable for transmission to a JSON-RPC client.
|
||||||
|
func MarshalResponse(id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) {
|
||||||
|
marshalledResult, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response, err := NewResponse(id, marshalledResult, rpcErr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(&response)
|
||||||
|
}
|
161
btcjson/v2/btcjson/jsonrpc_test.go
Normal file
161
btcjson/v2/btcjson/jsonrpc_test.go
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestIsValidIDType ensures the IsValidIDType function behaves as expected.
|
||||||
|
func TestIsValidIDType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id interface{}
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{"int", int(1), true},
|
||||||
|
{"int8", int8(1), true},
|
||||||
|
{"int16", int16(1), true},
|
||||||
|
{"int32", int32(1), true},
|
||||||
|
{"int64", int64(1), true},
|
||||||
|
{"uint", uint(1), true},
|
||||||
|
{"uint8", uint8(1), true},
|
||||||
|
{"uint16", uint16(1), true},
|
||||||
|
{"uint32", uint32(1), true},
|
||||||
|
{"uint64", uint64(1), true},
|
||||||
|
{"string", "1", true},
|
||||||
|
{"nil", nil, true},
|
||||||
|
{"float32", float32(1), true},
|
||||||
|
{"float64", float64(1), true},
|
||||||
|
{"bool", true, false},
|
||||||
|
{"chan int", make(chan int), false},
|
||||||
|
{"complex64", complex64(1), false},
|
||||||
|
{"complex128", complex128(1), false},
|
||||||
|
{"func", func() {}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
if btcjson.IsValidIDType(test.id) != test.isValid {
|
||||||
|
t.Errorf("Test #%d (%s) valid mismatch - got %v, "+
|
||||||
|
"want %v", i, test.name, !test.isValid,
|
||||||
|
test.isValid)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMarshalResponse ensures the MarshalResponse function works as expected.
|
||||||
|
func TestMarshalResponse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testID := 1
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
result interface{}
|
||||||
|
jsonErr *btcjson.RPCError
|
||||||
|
expected []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ordinary bool result with no error",
|
||||||
|
result: true,
|
||||||
|
jsonErr: nil,
|
||||||
|
expected: []byte(`{"result":true,"error":null,"id":1}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "result with error",
|
||||||
|
result: nil,
|
||||||
|
jsonErr: func() *btcjson.RPCError {
|
||||||
|
return btcjson.NewRPCError(btcjson.ErrRPCBlockNotFound, "123 not found")
|
||||||
|
}(),
|
||||||
|
expected: []byte(`{"result":null,"error":{"code":-5,"message":"123 not found"},"id":1}`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
_, _ = i, test
|
||||||
|
marshalled, err := btcjson.MarshalResponse(testID, test.result, test.jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(marshalled, test.expected) {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched result - got %s, "+
|
||||||
|
"want %s", i, test.name, marshalled,
|
||||||
|
test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMiscErrors tests a few error conditions not covered elsewhere.
|
||||||
|
func TestMiscErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Force an error in NewRequest by giving it a parameter type that is
|
||||||
|
// not supported.
|
||||||
|
_, err := btcjson.NewRequest(nil, "test", []interface{}{make(chan int)})
|
||||||
|
if err == nil {
|
||||||
|
t.Error("NewRequest: did not receive error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force an error in MarshalResponse by giving it an id type that is not
|
||||||
|
// supported.
|
||||||
|
wantErr := btcjson.Error{ErrorCode: btcjson.ErrInvalidType}
|
||||||
|
_, err = btcjson.MarshalResponse(make(chan int), nil, nil)
|
||||||
|
if jerr, ok := err.(btcjson.Error); !ok || jerr.ErrorCode != wantErr.ErrorCode {
|
||||||
|
t.Errorf("MarshalResult: did not receive expected error - got "+
|
||||||
|
"%v (%[1]T), want %v (%[2]T)", err, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force an error in MarshalResponse by giving it a result type that
|
||||||
|
// can't be marshalled.
|
||||||
|
_, err = btcjson.MarshalResponse(1, make(chan int), nil)
|
||||||
|
if _, ok := err.(*json.UnsupportedTypeError); !ok {
|
||||||
|
wantErr := &json.UnsupportedTypeError{}
|
||||||
|
t.Errorf("MarshalResult: did not receive expected error - got "+
|
||||||
|
"%v (%[1]T), want %T", err, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRPCError tests the error output for the RPCError type.
|
||||||
|
func TestRPCError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcjson.RPCError
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
btcjson.ErrRPCInvalidRequest,
|
||||||
|
"-32600: Invalid request",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
btcjson.ErrRPCMethodNotFound,
|
||||||
|
"-32601: Method not found",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.Error()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("Error #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
btcjson/v2/btcjson/jsonrpcerr.go
Normal file
83
btcjson/v2/btcjson/jsonrpcerr.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
// Standard JSON-RPC 2.0 errors.
|
||||||
|
var (
|
||||||
|
ErrRPCInvalidRequest = &RPCError{
|
||||||
|
Code: -32600,
|
||||||
|
Message: "Invalid request",
|
||||||
|
}
|
||||||
|
ErrRPCMethodNotFound = &RPCError{
|
||||||
|
Code: -32601,
|
||||||
|
Message: "Method not found",
|
||||||
|
}
|
||||||
|
ErrRPCInvalidParams = &RPCError{
|
||||||
|
Code: -32602,
|
||||||
|
Message: "Invalid parameters",
|
||||||
|
}
|
||||||
|
ErrRPCInternal = &RPCError{
|
||||||
|
Code: -32603,
|
||||||
|
Message: "Internal error",
|
||||||
|
}
|
||||||
|
ErrRPCParse = &RPCError{
|
||||||
|
Code: -32700,
|
||||||
|
Message: "Parse error",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// General application defined JSON errors.
|
||||||
|
const (
|
||||||
|
ErrRPCMisc RPCErrorCode = -1
|
||||||
|
ErrRPCForbiddenBySafeMode RPCErrorCode = -2
|
||||||
|
ErrRPCType RPCErrorCode = -3
|
||||||
|
ErrRPCInvalidAddressOrKey RPCErrorCode = -5
|
||||||
|
ErrRPCOutOfMemory RPCErrorCode = -7
|
||||||
|
ErrRPCInvalidParameter RPCErrorCode = -8
|
||||||
|
ErrRPCDatabase RPCErrorCode = -20
|
||||||
|
ErrRPCDeserialization RPCErrorCode = -22
|
||||||
|
ErrRPCVerify RPCErrorCode = -25
|
||||||
|
)
|
||||||
|
|
||||||
|
// Peer-to-peer client errors.
|
||||||
|
const (
|
||||||
|
ErrRPCClientNotConnected RPCErrorCode = -9
|
||||||
|
ErrRPCClientInInitialDownload RPCErrorCode = -10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wallet JSON errors
|
||||||
|
const (
|
||||||
|
ErrRPCWallet RPCErrorCode = -4
|
||||||
|
ErrRPCWalletInsufficientFunds RPCErrorCode = -6
|
||||||
|
ErrRPCWalletInvalidAccountName RPCErrorCode = -11
|
||||||
|
ErrRPCWalletKeypoolRanOut RPCErrorCode = -12
|
||||||
|
ErrRPCWalletUnlockNeeded RPCErrorCode = -13
|
||||||
|
ErrRPCWalletPassphraseIncorrect RPCErrorCode = -14
|
||||||
|
ErrRPCWalletWrongEncState RPCErrorCode = -15
|
||||||
|
ErrRPCWalletEncryptionFailed RPCErrorCode = -16
|
||||||
|
ErrRPCWalletAlreadyUnlocked RPCErrorCode = -17
|
||||||
|
)
|
||||||
|
|
||||||
|
// Specific Errors related to commands. These are the ones a user of the RPC
|
||||||
|
// server are most likely to see. Generally, the codes should match one of the
|
||||||
|
// more general errors above.
|
||||||
|
const (
|
||||||
|
ErrRPCBlockNotFound RPCErrorCode = -5
|
||||||
|
ErrRPCBlockCount RPCErrorCode = -5
|
||||||
|
ErrRPCBestBlockHash RPCErrorCode = -5
|
||||||
|
ErrRPCDifficulty RPCErrorCode = -5
|
||||||
|
ErrRPCOutOfRange RPCErrorCode = -1
|
||||||
|
ErrRPCNoTxInfo RPCErrorCode = -5
|
||||||
|
ErrRPCNoNewestBlockInfo RPCErrorCode = -5
|
||||||
|
ErrRPCInvalidTxVout RPCErrorCode = -5
|
||||||
|
ErrRPCRawTxString RPCErrorCode = -32602
|
||||||
|
ErrRPCDecodeHexString RPCErrorCode = -22
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errors that are specific to btcd.
|
||||||
|
const (
|
||||||
|
ErrRPCNoWallet RPCErrorCode = -1
|
||||||
|
ErrRPCUnimplemented RPCErrorCode = -1
|
||||||
|
)
|
292
btcjson/v2/btcjson/register.go
Normal file
292
btcjson/v2/btcjson/register.go
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsageFlag define flags that specify additional properties about the
|
||||||
|
// circumstances under which a command can be used.
|
||||||
|
type UsageFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UFWalletOnly indicates that the command can only be used with an RPC
|
||||||
|
// server that supports wallet commands.
|
||||||
|
UFWalletOnly UsageFlag = 1 << iota
|
||||||
|
|
||||||
|
// UFWebsocketOnly indicates that the command can only be used when
|
||||||
|
// communicating with an RPC server over websockets. This typically
|
||||||
|
// applies to notifications and notification registration functions
|
||||||
|
// since neiher makes since when using a single-shot HTTP-POST request.
|
||||||
|
UFWebsocketOnly
|
||||||
|
|
||||||
|
// UFNotification indicates that the command is actually a notification.
|
||||||
|
// This means when it is marshalled, the ID must be nil.
|
||||||
|
UFNotification
|
||||||
|
|
||||||
|
// highestUsageFlagBit is the maximum usage flag bit and is used in the
|
||||||
|
// stringer and tests to ensure all of the above constants have been
|
||||||
|
// tested.
|
||||||
|
highestUsageFlagBit
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of UsageFlag values back to their constant names for pretty printing.
|
||||||
|
var usageFlagStrings = map[UsageFlag]string{
|
||||||
|
UFWalletOnly: "UFWalletOnly",
|
||||||
|
UFWebsocketOnly: "UFWebsocketOnly",
|
||||||
|
UFNotification: "UFNotification",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the UsageFlag in human-readable form.
|
||||||
|
func (fl UsageFlag) String() string {
|
||||||
|
// No flags are set.
|
||||||
|
if fl == 0 {
|
||||||
|
return "0x0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add individual bit flags.
|
||||||
|
s := ""
|
||||||
|
for flag := UFWalletOnly; flag < highestUsageFlagBit; flag <<= 1 {
|
||||||
|
if fl&flag == flag {
|
||||||
|
s += usageFlagStrings[flag] + "|"
|
||||||
|
fl -= flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add remaining value as raw hex.
|
||||||
|
s = strings.TrimRight(s, "|")
|
||||||
|
if fl != 0 {
|
||||||
|
s += "|0x" + strconv.FormatUint(uint64(fl), 16)
|
||||||
|
}
|
||||||
|
s = strings.TrimLeft(s, "|")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodInfo keeps track of information about each registered method such as
|
||||||
|
// the parameter information.
|
||||||
|
type methodInfo struct {
|
||||||
|
maxParams int
|
||||||
|
numReqParams int
|
||||||
|
numOptParams int
|
||||||
|
defaults map[int]reflect.Value
|
||||||
|
flags UsageFlag
|
||||||
|
usage string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// These fields are used to map the registered types to method names.
|
||||||
|
registerLock sync.RWMutex
|
||||||
|
methodToConcreteType = make(map[string]reflect.Type)
|
||||||
|
methodToInfo = make(map[string]methodInfo)
|
||||||
|
concreteTypeToMethod = make(map[reflect.Type]string)
|
||||||
|
)
|
||||||
|
|
||||||
|
// baseKindString returns the base kind for a given reflect.Type after
|
||||||
|
// indirecting through all pointers.
|
||||||
|
func baseKindString(rt reflect.Type) string {
|
||||||
|
numIndirects := 0
|
||||||
|
for rt.Kind() == reflect.Ptr {
|
||||||
|
numIndirects++
|
||||||
|
rt = rt.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s%s", strings.Repeat("*", numIndirects), rt.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAcceptableKind returns whether or not the passed field type is a supported
|
||||||
|
// type. It is called after the first pointer indirection, so further pointers
|
||||||
|
// are not supported.
|
||||||
|
func isAcceptableKind(kind reflect.Kind) bool {
|
||||||
|
switch kind {
|
||||||
|
case reflect.Chan:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Complex64:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Complex128:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Func:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Ptr:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Interface:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCmd registers a new command that will automatically marshal to and
|
||||||
|
// from JSON-RPC with full type checking and positional parameter support. It
|
||||||
|
// also accepts usage flags which identify the circumstances under which the
|
||||||
|
// command can be used.
|
||||||
|
//
|
||||||
|
// This package automatically registers all of the exported commands by default
|
||||||
|
// using this function, however it is also exported so callers can easily
|
||||||
|
// register custom types.
|
||||||
|
//
|
||||||
|
// The type format is very strict since it needs to be able to automatically
|
||||||
|
// marshal to and from JSON-RPC 1.0. The following enumerates the requirements:
|
||||||
|
//
|
||||||
|
// - The provided command must be a single pointer to a struct
|
||||||
|
// - All fields must be exported
|
||||||
|
// - The order of the positional parameters in the marshalled JSON will be in
|
||||||
|
// the same order as declared in the struct definition
|
||||||
|
// - Struct embedding is not supported
|
||||||
|
// - Struct fields may NOT be channels, functions, complex, or interface
|
||||||
|
// - A field in the provided struct with a pointer is treated as optional
|
||||||
|
// - Multiple indirections (i.e **int) are not supported
|
||||||
|
// - Once the first optional field (pointer) is encountered, the remaining
|
||||||
|
// fields must also be optional fields (pointers) as required by positional
|
||||||
|
// params
|
||||||
|
// - A field that has a 'jsonrpcdefault' struct tag must be an optional field
|
||||||
|
// (pointer)
|
||||||
|
//
|
||||||
|
// NOTE: This function only needs to be able to examine the structure of the
|
||||||
|
// passed struct, so it does not need to be an actual instance. Therefore, it
|
||||||
|
// is recommended to simply pass a nil pointer cast to the appropriate type.
|
||||||
|
// For example, (*FooCmd)(nil).
|
||||||
|
func RegisterCmd(method string, cmd interface{}, flags UsageFlag) error {
|
||||||
|
registerLock.Lock()
|
||||||
|
defer registerLock.Unlock()
|
||||||
|
|
||||||
|
if _, ok := methodToConcreteType[method]; ok {
|
||||||
|
str := fmt.Sprintf("method %q is already registered", method)
|
||||||
|
return makeError(ErrDuplicateMethod, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that no unrecognized flag bits were specified.
|
||||||
|
if ^(highestUsageFlagBit-1)&flags != 0 {
|
||||||
|
str := fmt.Sprintf("invalid usage flags specified for method "+
|
||||||
|
"%s: %v", method, flags)
|
||||||
|
return makeError(ErrInvalidUsageFlags, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
rtp := reflect.TypeOf(cmd)
|
||||||
|
if rtp.Kind() != reflect.Ptr {
|
||||||
|
str := fmt.Sprintf("type must be *struct not '%s (%s)'", rtp,
|
||||||
|
rtp.Kind())
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
rt := rtp.Elem()
|
||||||
|
if rt.Kind() != reflect.Struct {
|
||||||
|
str := fmt.Sprintf("type must be *struct not '%s (*%s)'",
|
||||||
|
rtp, rt.Kind())
|
||||||
|
return makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enumerate the struct fields to validate them and gather parameter
|
||||||
|
// information.
|
||||||
|
numFields := rt.NumField()
|
||||||
|
numOptFields := 0
|
||||||
|
defaults := make(map[int]reflect.Value)
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
rtf := rt.Field(i)
|
||||||
|
if rtf.Anonymous {
|
||||||
|
str := fmt.Sprintf("embedded fields are not supported "+
|
||||||
|
"(field name: %q)", rtf.Name)
|
||||||
|
return makeError(ErrEmbeddedType, str)
|
||||||
|
}
|
||||||
|
if rtf.PkgPath != "" {
|
||||||
|
str := fmt.Sprintf("unexported fields are not supported "+
|
||||||
|
"(field name: %q)", rtf.Name)
|
||||||
|
return makeError(ErrUnexportedField, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow types that can't be JSON encoded. Also, determine
|
||||||
|
// if the field is optional based on it being a pointer.
|
||||||
|
var isOptional bool
|
||||||
|
switch kind := rtf.Type.Kind(); kind {
|
||||||
|
case reflect.Ptr:
|
||||||
|
isOptional = true
|
||||||
|
kind = rtf.Type.Elem().Kind()
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
if !isAcceptableKind(kind) {
|
||||||
|
str := fmt.Sprintf("unsupported field type "+
|
||||||
|
"'%s (%s)' (field name %q)", rtf.Type,
|
||||||
|
baseKindString(rtf.Type), rtf.Name)
|
||||||
|
return makeError(ErrUnsupportedFieldType, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the optional fields and ensure all fields after the
|
||||||
|
// first optional field are also optional.
|
||||||
|
if isOptional {
|
||||||
|
numOptFields++
|
||||||
|
} else {
|
||||||
|
if numOptFields > 0 {
|
||||||
|
str := fmt.Sprintf("all fields after the first "+
|
||||||
|
"optional field must also be optional "+
|
||||||
|
"(field name %q)", rtf.Name)
|
||||||
|
return makeError(ErrNonOptionalField, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the default value can be unsmarshalled into the type
|
||||||
|
// and that defaults are only specified for optional fields.
|
||||||
|
if tag := rtf.Tag.Get("jsonrpcdefault"); tag != "" {
|
||||||
|
if !isOptional {
|
||||||
|
str := fmt.Sprintf("required fields must not "+
|
||||||
|
"have a default specified (field name "+
|
||||||
|
"%q)", rtf.Name)
|
||||||
|
return makeError(ErrNonOptionalDefault, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
rvf := reflect.New(rtf.Type.Elem())
|
||||||
|
err := json.Unmarshal([]byte(tag), rvf.Interface())
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("default value of %q is "+
|
||||||
|
"the wrong type (field name %q)", tag,
|
||||||
|
rtf.Name)
|
||||||
|
return makeError(ErrMismatchedDefault, str)
|
||||||
|
}
|
||||||
|
defaults[i] = rvf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the registration maps.
|
||||||
|
methodToConcreteType[method] = rtp
|
||||||
|
methodToInfo[method] = methodInfo{
|
||||||
|
maxParams: numFields,
|
||||||
|
numReqParams: numFields - numOptFields,
|
||||||
|
numOptParams: numOptFields,
|
||||||
|
defaults: defaults,
|
||||||
|
flags: flags,
|
||||||
|
}
|
||||||
|
concreteTypeToMethod[rtp] = method
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustRegisterCmd performs the same function as RegisterCmd except it panics
|
||||||
|
// if there is an error. This should only be called from package init
|
||||||
|
// functions.
|
||||||
|
func MustRegisterCmd(method string, cmd interface{}, flags UsageFlag) {
|
||||||
|
if err := RegisterCmd(method, cmd, flags); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to register type %q: %v\n", method,
|
||||||
|
err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisteredCmdMethods returns a sorted list of methods for all registered
|
||||||
|
// commands.
|
||||||
|
func RegisteredCmdMethods() []string {
|
||||||
|
registerLock.Lock()
|
||||||
|
defer registerLock.Unlock()
|
||||||
|
|
||||||
|
methods := make([]string, 0, len(methodToInfo))
|
||||||
|
for k := range methodToInfo {
|
||||||
|
methods = append(methods, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sort.StringSlice(methods))
|
||||||
|
return methods
|
||||||
|
}
|
263
btcjson/v2/btcjson/register_test.go
Normal file
263
btcjson/v2/btcjson/register_test.go
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestUsageFlagStringer tests the stringized output for the UsageFlag type.
|
||||||
|
func TestUsageFlagStringer(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in btcjson.UsageFlag
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "0x0"},
|
||||||
|
{btcjson.UFWalletOnly, "UFWalletOnly"},
|
||||||
|
{btcjson.UFWebsocketOnly, "UFWebsocketOnly"},
|
||||||
|
{btcjson.UFNotification, "UFNotification"},
|
||||||
|
{btcjson.UFWalletOnly | btcjson.UFWebsocketOnly,
|
||||||
|
"UFWalletOnly|UFWebsocketOnly"},
|
||||||
|
{btcjson.UFWalletOnly | btcjson.UFWebsocketOnly | (1 << 31),
|
||||||
|
"UFWalletOnly|UFWebsocketOnly|0x80000000"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect additional usage flags that don't have the stringer added.
|
||||||
|
numUsageFlags := 0
|
||||||
|
highestUsageFlagBit := btcjson.TstHighestUsageFlagBit
|
||||||
|
for highestUsageFlagBit > 1 {
|
||||||
|
numUsageFlags++
|
||||||
|
highestUsageFlagBit >>= 1
|
||||||
|
}
|
||||||
|
if len(tests)-3 != numUsageFlags {
|
||||||
|
t.Errorf("It appears a usage flag was added without adding " +
|
||||||
|
"an associated stringer test")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.String()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRegisterCmdErrors ensures the RegisterCmd function returns the expected
|
||||||
|
// error when provided with invalid types.
|
||||||
|
func TestRegisterCmdErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
cmdFunc func() interface{}
|
||||||
|
flags btcjson.UsageFlag
|
||||||
|
err btcjson.Error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "duplicate method",
|
||||||
|
method: "getblock",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
return struct{}{}
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrDuplicateMethod},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid usage flags",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
flags: btcjson.TstHighestUsageFlagBit,
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidUsageFlags},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid type",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid type 2",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
return &[]string{}
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "embedded field",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct{ int }
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrEmbeddedType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unexported field",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct{ a int }
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnexportedField},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported field type 1",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct{ A **int }
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported field type 2",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct{ A chan int }
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported field type 3",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct{ A complex64 }
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported field type 4",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct{ A complex128 }
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported field type 5",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct{ A func() }
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported field type 6",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct{ A interface{} }
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "required after optional",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct {
|
||||||
|
A *int
|
||||||
|
B int
|
||||||
|
}
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrNonOptionalField},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-optional with default",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct {
|
||||||
|
A int `jsonrpcdefault:"1"`
|
||||||
|
}
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrNonOptionalDefault},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mismatched default",
|
||||||
|
method: "registertestcmd",
|
||||||
|
cmdFunc: func() interface{} {
|
||||||
|
type test struct {
|
||||||
|
A *int `jsonrpcdefault:"1.7"`
|
||||||
|
}
|
||||||
|
return (*test)(nil)
|
||||||
|
},
|
||||||
|
err: btcjson.Error{ErrorCode: btcjson.ErrMismatchedDefault},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
err := btcjson.RegisterCmd(test.method, test.cmdFunc(),
|
||||||
|
test.flags)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Test #%d (%s) wrong error - got %T, "+
|
||||||
|
"want %T", i, test.name, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||||
|
if gotErrorCode != test.err.ErrorCode {
|
||||||
|
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||||
|
"%v, want %v", i, test.name, gotErrorCode,
|
||||||
|
test.err.ErrorCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMustRegisterCmdPanic ensures the MustRegisterCmd function panics when
|
||||||
|
// used to register an invalid type.
|
||||||
|
func TestMustRegisterCmdPanic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Setup a defer to catch the expected panic to ensure it actually
|
||||||
|
// paniced.
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err == nil {
|
||||||
|
t.Error("MustRegisterCmd did not panic as expected")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Intentionally try to register an invalid type to force a panic.
|
||||||
|
btcjson.MustRegisterCmd("panicme", 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRegisteredCmdMethods tests the RegisteredCmdMethods function ensure it
|
||||||
|
// works as expected.
|
||||||
|
func TestRegisteredCmdMethods(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Ensure the registerd methods are returned.
|
||||||
|
methods := btcjson.RegisteredCmdMethods()
|
||||||
|
if len(methods) == 0 {
|
||||||
|
t.Fatal("RegisteredCmdMethods: no methods")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the returnd methods are sorted.
|
||||||
|
sortedMethods := make([]string, len(methods))
|
||||||
|
copy(sortedMethods, methods)
|
||||||
|
sort.Sort(sort.StringSlice(sortedMethods))
|
||||||
|
if !reflect.DeepEqual(sortedMethods, methods) {
|
||||||
|
t.Fatal("RegisteredCmdMethods: methods are not sorted")
|
||||||
|
}
|
||||||
|
}
|
675
btcjson/v2/btcjson/walletsvrcmds.go
Normal file
675
btcjson/v2/btcjson/walletsvrcmds.go
Normal file
|
@ -0,0 +1,675 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||||
|
// a wallet server.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
// AddMultisigAddressCmd defines the addmutisigaddress JSON-RPC command.
|
||||||
|
type AddMultisigAddressCmd struct {
|
||||||
|
NRequired int
|
||||||
|
Keys []string
|
||||||
|
Account *string `jsonrpcdefault:"\"\""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAddMultisigAddressCmd returns a new instance which can be used to issue a
|
||||||
|
// addmultisigaddress JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewAddMultisigAddressCmd(nRequired int, keys []string, account *string) *AddMultisigAddressCmd {
|
||||||
|
return &AddMultisigAddressCmd{
|
||||||
|
NRequired: nRequired,
|
||||||
|
Keys: keys,
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMultisigCmd defines the createmultisig JSON-RPC command.
|
||||||
|
type CreateMultisigCmd struct {
|
||||||
|
NRequired int
|
||||||
|
Keys []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateMultisigCmd returns a new instance which can be used to issue a
|
||||||
|
// createmultisig JSON-RPC command.
|
||||||
|
func NewCreateMultisigCmd(nRequired int, keys []string) *CreateMultisigCmd {
|
||||||
|
return &CreateMultisigCmd{
|
||||||
|
NRequired: nRequired,
|
||||||
|
Keys: keys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpPrivKeyCmd defines the dumpprivkey JSON-RPC command.
|
||||||
|
type DumpPrivKeyCmd struct {
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDumpPrivKeyCmd returns a new instance which can be used to issue a
|
||||||
|
// dumpprivkey JSON-RPC command.
|
||||||
|
func NewDumpPrivKeyCmd(address string) *DumpPrivKeyCmd {
|
||||||
|
return &DumpPrivKeyCmd{
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptWalletCmd defines the encryptwallet JSON-RPC command.
|
||||||
|
type EncryptWalletCmd struct {
|
||||||
|
Passphrase string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncryptWalletCmd returns a new instance which can be used to issue a
|
||||||
|
// encryptwallet JSON-RPC command.
|
||||||
|
func NewEncryptWalletCmd(passphrase string) *EncryptWalletCmd {
|
||||||
|
return &EncryptWalletCmd{
|
||||||
|
Passphrase: passphrase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EstimateFeeCmd defines the estimatefee JSON-RPC command.
|
||||||
|
type EstimateFeeCmd struct {
|
||||||
|
NumBlocks int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEstimateFeeCmd returns a new instance which can be used to issue a
|
||||||
|
// estimatefee JSON-RPC command.
|
||||||
|
func NewEstimateFeeCmd(numBlocks int64) *EstimateFeeCmd {
|
||||||
|
return &EstimateFeeCmd{
|
||||||
|
NumBlocks: numBlocks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EstimatePriorityCmd defines the estimatepriority JSON-RPC command.
|
||||||
|
type EstimatePriorityCmd struct {
|
||||||
|
NumBlocks int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEstimatePriorityCmd returns a new instance which can be used to issue a
|
||||||
|
// estimatepriority JSON-RPC command.
|
||||||
|
func NewEstimatePriorityCmd(numBlocks int64) *EstimatePriorityCmd {
|
||||||
|
return &EstimatePriorityCmd{
|
||||||
|
NumBlocks: numBlocks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountCmd defines the getaccount JSON-RPC command.
|
||||||
|
type GetAccountCmd struct {
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetAccountCmd returns a new instance which can be used to issue a
|
||||||
|
// getaccount JSON-RPC command.
|
||||||
|
func NewGetAccountCmd(address string) *GetAccountCmd {
|
||||||
|
return &GetAccountCmd{
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountAddressCmd defines the getaccountaddress JSON-RPC command.
|
||||||
|
type GetAccountAddressCmd struct {
|
||||||
|
Account string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetAccountAddressCmd returns a new instance which can be used to issue a
|
||||||
|
// getaccountaddress JSON-RPC command.
|
||||||
|
func NewGetAccountAddressCmd(account string) *GetAccountAddressCmd {
|
||||||
|
return &GetAccountAddressCmd{
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddressesByAccountCmd defines the getaddressesbyaccount JSON-RPC command.
|
||||||
|
type GetAddressesByAccountCmd struct {
|
||||||
|
Account string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetAddressesByAccountCmd returns a new instance which can be used to issue
|
||||||
|
// a getaddressesbyaccount JSON-RPC command.
|
||||||
|
func NewGetAddressesByAccountCmd(account string) *GetAddressesByAccountCmd {
|
||||||
|
return &GetAddressesByAccountCmd{
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBalanceCmd defines the getbalance JSON-RPC command.
|
||||||
|
type GetBalanceCmd struct {
|
||||||
|
Account *string `jsonrpcdefault:"\"*\""`
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetBalanceCmd returns a new instance which can be used to issue a
|
||||||
|
// getbalance JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetBalanceCmd(account *string, minConf *int) *GetBalanceCmd {
|
||||||
|
return &GetBalanceCmd{
|
||||||
|
Account: account,
|
||||||
|
MinConf: minConf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNewAddressCmd defines the getnewaddress JSON-RPC command.
|
||||||
|
type GetNewAddressCmd struct {
|
||||||
|
Account *string `jsonrpcdefault:"\"\""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetNewAddressCmd returns a new instance which can be used to issue a
|
||||||
|
// getnewaddress JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetNewAddressCmd(account *string) *GetNewAddressCmd {
|
||||||
|
return &GetNewAddressCmd{
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawChangeAddressCmd defines the getrawchangeaddress JSON-RPC command.
|
||||||
|
type GetRawChangeAddressCmd struct {
|
||||||
|
Account *string `jsonrpcdefault:"\"\""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetRawChangeAddressCmd returns a new instance which can be used to issue a
|
||||||
|
// getrawchangeaddress JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetRawChangeAddressCmd(account *string) *GetRawChangeAddressCmd {
|
||||||
|
return &GetRawChangeAddressCmd{
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReceivedByAccountCmd defines the getreceivedbyaccount JSON-RPC command.
|
||||||
|
type GetReceivedByAccountCmd struct {
|
||||||
|
Account string
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetReceivedByAccountCmd returns a new instance which can be used to issue
|
||||||
|
// a getreceivedbyaccount JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetReceivedByAccountCmd(account string, minConf *int) *GetReceivedByAccountCmd {
|
||||||
|
return &GetReceivedByAccountCmd{
|
||||||
|
Account: account,
|
||||||
|
MinConf: minConf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReceivedByAddressCmd defines the getreceivedbyaddress JSON-RPC command.
|
||||||
|
type GetReceivedByAddressCmd struct {
|
||||||
|
Address string
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetReceivedByAddressCmd returns a new instance which can be used to issue
|
||||||
|
// a getreceivedbyaddress JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetReceivedByAddressCmd(address string, minConf *int) *GetReceivedByAddressCmd {
|
||||||
|
return &GetReceivedByAddressCmd{
|
||||||
|
Address: address,
|
||||||
|
MinConf: minConf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionCmd defines the gettransaction JSON-RPC command.
|
||||||
|
type GetTransactionCmd struct {
|
||||||
|
Txid string
|
||||||
|
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetTransactionCmd returns a new instance which can be used to issue a
|
||||||
|
// gettransaction JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetTransactionCmd(txHash string, includeWatchOnly *bool) *GetTransactionCmd {
|
||||||
|
return &GetTransactionCmd{
|
||||||
|
Txid: txHash,
|
||||||
|
IncludeWatchOnly: includeWatchOnly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPrivKeyCmd defines the importprivkey JSON-RPC command.
|
||||||
|
type ImportPrivKeyCmd struct {
|
||||||
|
PrivKey string
|
||||||
|
Label *string `jsonrpcdefault:"\"\""`
|
||||||
|
Rescan *bool `jsonrpcdefault:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImportPrivKeyCmd returns a new instance which can be used to issue a
|
||||||
|
// importprivkey JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewImportPrivKeyCmd(privKey string, label *string, rescan *bool) *ImportPrivKeyCmd {
|
||||||
|
return &ImportPrivKeyCmd{
|
||||||
|
PrivKey: privKey,
|
||||||
|
Label: label,
|
||||||
|
Rescan: rescan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyPoolRefillCmd defines the keypoolrefill JSON-RPC command.
|
||||||
|
type KeyPoolRefillCmd struct {
|
||||||
|
NewSize *uint `jsonrpcdefault:"100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyPoolRefillCmd returns a new instance which can be used to issue a
|
||||||
|
// keypoolrefill JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewKeyPoolRefillCmd(newSize *uint) *KeyPoolRefillCmd {
|
||||||
|
return &KeyPoolRefillCmd{
|
||||||
|
NewSize: newSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccountsCmd defines the listaccounts JSON-RPC command.
|
||||||
|
type ListAccountsCmd struct {
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListAccountsCmd returns a new instance which can be used to issue a
|
||||||
|
// listaccounts JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewListAccountsCmd(minConf *int) *ListAccountsCmd {
|
||||||
|
return &ListAccountsCmd{
|
||||||
|
MinConf: minConf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAddressGroupingsCmd defines the listaddressgroupings JSON-RPC command.
|
||||||
|
type ListAddressGroupingsCmd struct{}
|
||||||
|
|
||||||
|
// NewListAddressGroupingsCmd returns a new instance which can be used to issue
|
||||||
|
// a listaddressgroupoings JSON-RPC command.
|
||||||
|
func NewListAddressGroupingsCmd() *ListAddressGroupingsCmd {
|
||||||
|
return &ListAddressGroupingsCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLockUnspentCmd defines the listlockunspent JSON-RPC command.
|
||||||
|
type ListLockUnspentCmd struct{}
|
||||||
|
|
||||||
|
// NewListLockUnspentCmd returns a new instance which can be used to issue a
|
||||||
|
// listlockunspent JSON-RPC command.
|
||||||
|
func NewListLockUnspentCmd() *ListLockUnspentCmd {
|
||||||
|
return &ListLockUnspentCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReceivedByAccountCmd defines the listreceivedbyaccount JSON-RPC command.
|
||||||
|
type ListReceivedByAccountCmd struct {
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
IncludeEmpty *bool `jsonrpcdefault:"false"`
|
||||||
|
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListReceivedByAccountCmd returns a new instance which can be used to issue
|
||||||
|
// a listreceivedbyaccount JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewListReceivedByAccountCmd(minConf *int, includeEmpty, includeWatchOnly *bool) *ListReceivedByAccountCmd {
|
||||||
|
return &ListReceivedByAccountCmd{
|
||||||
|
MinConf: minConf,
|
||||||
|
IncludeEmpty: includeEmpty,
|
||||||
|
IncludeWatchOnly: includeWatchOnly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReceivedByAddressCmd defines the listreceivedbyaddress JSON-RPC command.
|
||||||
|
type ListReceivedByAddressCmd struct {
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
IncludeEmpty *bool `jsonrpcdefault:"false"`
|
||||||
|
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListReceivedByAddressCmd returns a new instance which can be used to issue
|
||||||
|
// a listreceivedbyaddress JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewListReceivedByAddressCmd(minConf *int, includeEmpty, includeWatchOnly *bool) *ListReceivedByAddressCmd {
|
||||||
|
return &ListReceivedByAddressCmd{
|
||||||
|
MinConf: minConf,
|
||||||
|
IncludeEmpty: includeEmpty,
|
||||||
|
IncludeWatchOnly: includeWatchOnly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSinceBlockCmd defines the listsinceblock JSON-RPC command.
|
||||||
|
type ListSinceBlockCmd struct {
|
||||||
|
BlockHash *string
|
||||||
|
TargetConfirmations *int `jsonrpcdefault:"1"`
|
||||||
|
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListSinceBlockCmd returns a new instance which can be used to issue a
|
||||||
|
// listsinceblock JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewListSinceBlockCmd(blockHash *string, targetConfirms *int, includeWatchOnly *bool) *ListSinceBlockCmd {
|
||||||
|
return &ListSinceBlockCmd{
|
||||||
|
BlockHash: blockHash,
|
||||||
|
TargetConfirmations: targetConfirms,
|
||||||
|
IncludeWatchOnly: includeWatchOnly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTransactionsCmd defines the listtransactions JSON-RPC command.
|
||||||
|
type ListTransactionsCmd struct {
|
||||||
|
Account *string
|
||||||
|
Count *int `jsonrpcdefault:"10"`
|
||||||
|
From *int `jsonrpcdefault:"0"`
|
||||||
|
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListTransactionsCmd returns a new instance which can be used to issue a
|
||||||
|
// listtransactions JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewListTransactionsCmd(account *string, count, from *int, includeWatchOnly *bool) *ListTransactionsCmd {
|
||||||
|
return &ListTransactionsCmd{
|
||||||
|
Account: account,
|
||||||
|
Count: count,
|
||||||
|
From: from,
|
||||||
|
IncludeWatchOnly: includeWatchOnly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUnspentCmd defines the listunspent JSON-RPC command.
|
||||||
|
type ListUnspentCmd struct {
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
MaxConf *int `jsonrpcdefault:"9999999"`
|
||||||
|
Addresses *[]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListUnspentCmd returns a new instance which can be used to issue a
|
||||||
|
// listunspent JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewListUnspentCmd(minConf, maxConf *int, addresses *[]string) *ListUnspentCmd {
|
||||||
|
return &ListUnspentCmd{
|
||||||
|
MinConf: minConf,
|
||||||
|
MaxConf: maxConf,
|
||||||
|
Addresses: addresses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LockUnspentCmd defines the lockunspent JSON-RPC command.
|
||||||
|
type LockUnspentCmd struct {
|
||||||
|
Unlock bool
|
||||||
|
Transactions []TransactionInput
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLockUnspentCmd returns a new instance which can be used to issue a
|
||||||
|
// lockunspent JSON-RPC command.
|
||||||
|
func NewLockUnspentCmd(unlock bool, transactions []TransactionInput) *LockUnspentCmd {
|
||||||
|
return &LockUnspentCmd{
|
||||||
|
Unlock: unlock,
|
||||||
|
Transactions: transactions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveCmd defines the move JSON-RPC command.
|
||||||
|
type MoveCmd struct {
|
||||||
|
FromAccount string
|
||||||
|
ToAccount string
|
||||||
|
Amount float64 // In BTC
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
Comment *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMoveCmd returns a new instance which can be used to issue a move JSON-RPC
|
||||||
|
// command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewMoveCmd(fromAccount, toAccount string, amount float64, minConf *int, comment *string) *MoveCmd {
|
||||||
|
return &MoveCmd{
|
||||||
|
FromAccount: fromAccount,
|
||||||
|
ToAccount: toAccount,
|
||||||
|
Amount: amount,
|
||||||
|
MinConf: minConf,
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendFromCmd defines the sendfrom JSON-RPC command.
|
||||||
|
type SendFromCmd struct {
|
||||||
|
FromAccount string
|
||||||
|
ToAddress string
|
||||||
|
Amount float64 // In BTC
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
Comment *string
|
||||||
|
CommentTo *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSendFromCmd returns a new instance which can be used to issue a sendfrom
|
||||||
|
// JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewSendFromCmd(fromAccount, toAddress string, amount float64, minConf *int, comment, commentTo *string) *SendFromCmd {
|
||||||
|
return &SendFromCmd{
|
||||||
|
FromAccount: fromAccount,
|
||||||
|
ToAddress: toAddress,
|
||||||
|
Amount: amount,
|
||||||
|
MinConf: minConf,
|
||||||
|
Comment: comment,
|
||||||
|
CommentTo: commentTo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendManyCmd defines the sendmany JSON-RPC command.
|
||||||
|
type SendManyCmd struct {
|
||||||
|
FromAccount string
|
||||||
|
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
||||||
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
Comment *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSendManyCmd returns a new instance which can be used to issue a sendmany
|
||||||
|
// JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewSendManyCmd(fromAccount string, amounts map[string]float64, minConf *int, comment *string) *SendManyCmd {
|
||||||
|
return &SendManyCmd{
|
||||||
|
FromAccount: fromAccount,
|
||||||
|
Amounts: amounts,
|
||||||
|
MinConf: minConf,
|
||||||
|
Comment: comment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendToAddressCmd defines the sendtoaddress JSON-RPC command.
|
||||||
|
type SendToAddressCmd struct {
|
||||||
|
Address string
|
||||||
|
Amount float64
|
||||||
|
Comment *string
|
||||||
|
CommentTo *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSendToAddressCmd returns a new instance which can be used to issue a
|
||||||
|
// sendtoaddress JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewSendToAddressCmd(address string, amount float64, comment, commentTo *string) *SendToAddressCmd {
|
||||||
|
return &SendToAddressCmd{
|
||||||
|
Address: address,
|
||||||
|
Amount: amount,
|
||||||
|
Comment: comment,
|
||||||
|
CommentTo: commentTo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAccountCmd defines the setaccount JSON-RPC command.
|
||||||
|
type SetAccountCmd struct {
|
||||||
|
Address string
|
||||||
|
Account string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSetAccountCmd returns a new instance which can be used to issue a
|
||||||
|
// setaccount JSON-RPC command.
|
||||||
|
func NewSetAccountCmd(address, account string) *SetAccountCmd {
|
||||||
|
return &SetAccountCmd{
|
||||||
|
Address: address,
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTxFeeCmd defines the settxfee JSON-RPC command.
|
||||||
|
type SetTxFeeCmd struct {
|
||||||
|
Amount float64 // In BTC
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSetTxFeeCmd returns a new instance which can be used to issue a settxfee
|
||||||
|
// JSON-RPC command.
|
||||||
|
func NewSetTxFeeCmd(amount float64) *SetTxFeeCmd {
|
||||||
|
return &SetTxFeeCmd{
|
||||||
|
Amount: amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignMessageCmd defines the signmessage JSON-RPC command.
|
||||||
|
type SignMessageCmd struct {
|
||||||
|
Address string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSignMessageCmd returns a new instance which can be used to issue a
|
||||||
|
// signmessage JSON-RPC command.
|
||||||
|
func NewSignMessageCmd(address, message string) *SignMessageCmd {
|
||||||
|
return &SignMessageCmd{
|
||||||
|
Address: address,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawTxInput models the data needed for raw transaction input that is used in
|
||||||
|
// the SignRawTransactionCmd struct.
|
||||||
|
type RawTxInput struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
ScriptPubKey string `json:"scriptPubKey"`
|
||||||
|
RedeemScript string `json:"redeemScript"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignRawTransactionCmd defines the signrawtransaction JSON-RPC command.
|
||||||
|
type SignRawTransactionCmd struct {
|
||||||
|
RawTx string
|
||||||
|
Inputs *[]RawTxInput
|
||||||
|
PrivKeys *[]string
|
||||||
|
Flags *string `jsonrpcdefault:"\"ALL\""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSignRawTransactionCmd returns a new instance which can be used to issue a
|
||||||
|
// signrawtransaction JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewSignRawTransactionCmd(hexEncodedTx string, inputs *[]RawTxInput, privKeys *[]string, flags *string) *SignRawTransactionCmd {
|
||||||
|
return &SignRawTransactionCmd{
|
||||||
|
RawTx: hexEncodedTx,
|
||||||
|
Inputs: inputs,
|
||||||
|
PrivKeys: privKeys,
|
||||||
|
Flags: flags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletLockCmd defines the walletlock JSON-RPC command.
|
||||||
|
type WalletLockCmd struct{}
|
||||||
|
|
||||||
|
// NewWalletLockCmd returns a new instance which can be used to issue a
|
||||||
|
// walletlock JSON-RPC command.
|
||||||
|
func NewWalletLockCmd() *WalletLockCmd {
|
||||||
|
return &WalletLockCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletPassphraseCmd defines the walletpassphrase JSON-RPC command.
|
||||||
|
type WalletPassphraseCmd struct {
|
||||||
|
Passphrase string
|
||||||
|
Timeout int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWalletPassphraseCmd returns a new instance which can be used to issue a
|
||||||
|
// walletpassphrase JSON-RPC command.
|
||||||
|
func NewWalletPassphraseCmd(passphrase string, timeout int64) *WalletPassphraseCmd {
|
||||||
|
return &WalletPassphraseCmd{
|
||||||
|
Passphrase: passphrase,
|
||||||
|
Timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletPassphraseChangeCmd defines the walletpassphrase JSON-RPC command.
|
||||||
|
type WalletPassphraseChangeCmd struct {
|
||||||
|
OldPassphrase string
|
||||||
|
NewPassphrase string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWalletPassphraseChangeCmd returns a new instance which can be used to
|
||||||
|
// issue a walletpassphrasechange JSON-RPC command.
|
||||||
|
func NewWalletPassphraseChangeCmd(oldPassphrase, newPassphrase string) *WalletPassphraseChangeCmd {
|
||||||
|
return &WalletPassphraseChangeCmd{
|
||||||
|
OldPassphrase: oldPassphrase,
|
||||||
|
NewPassphrase: newPassphrase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// The commands in this file are only usable with a wallet server.
|
||||||
|
flags := UFWalletOnly
|
||||||
|
|
||||||
|
MustRegisterCmd("addmultisigaddress", (*AddMultisigAddressCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("createmultisig", (*CreateMultisigCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("dumpprivkey", (*DumpPrivKeyCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("encryptwallet", (*EncryptWalletCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("estimatefee", (*EstimateFeeCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("estimatepriority", (*EstimatePriorityCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getaccount", (*GetAccountCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getaccountaddress", (*GetAccountAddressCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getaddressesbyaccount", (*GetAddressesByAccountCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getbalance", (*GetBalanceCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getnewaddress", (*GetNewAddressCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getrawchangeaddress", (*GetRawChangeAddressCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getreceivedbyaccount", (*GetReceivedByAccountCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getreceivedbyaddress", (*GetReceivedByAddressCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("gettransaction", (*GetTransactionCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("importprivkey", (*ImportPrivKeyCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("keypoolrefill", (*KeyPoolRefillCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listaccounts", (*ListAccountsCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listaddressgroupings", (*ListAddressGroupingsCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listlockunspent", (*ListLockUnspentCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listreceivedbyaccount", (*ListReceivedByAccountCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listreceivedbyaddress", (*ListReceivedByAddressCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listsinceblock", (*ListSinceBlockCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listtransactions", (*ListTransactionsCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listunspent", (*ListUnspentCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("lockunspent", (*LockUnspentCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("move", (*MoveCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("sendfrom", (*SendFromCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("sendmany", (*SendManyCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("sendtoaddress", (*SendToAddressCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("setaccount", (*SetAccountCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("settxfee", (*SetTxFeeCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("signmessage", (*SignMessageCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("signrawtransaction", (*SignRawTransactionCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("walletlock", (*WalletLockCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("walletpassphrase", (*WalletPassphraseCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("walletpassphrasechange", (*WalletPassphraseChangeCmd)(nil), flags)
|
||||||
|
}
|
1250
btcjson/v2/btcjson/walletsvrcmds_test.go
Normal file
1250
btcjson/v2/btcjson/walletsvrcmds_test.go
Normal file
File diff suppressed because it is too large
Load diff
138
btcjson/v2/btcjson/walletsvrresults.go
Normal file
138
btcjson/v2/btcjson/walletsvrresults.go
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
// GetTransactionDetailsResult models the details data from the gettransaction command.
|
||||||
|
type GetTransactionDetailsResult struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Fee float64 `json:"fee,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionResult models the data from the gettransaction command.
|
||||||
|
type GetTransactionResult struct {
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Fee float64 `json:"fee,omitempty"`
|
||||||
|
Confirmations int64 `json:"confirmations"`
|
||||||
|
BlockHash string `json:"blockhash"`
|
||||||
|
BlockIndex int64 `json:"blockindex"`
|
||||||
|
BlockTime int64 `json:"blocktime"`
|
||||||
|
TxID string `json:"txid"`
|
||||||
|
WalletConflicts []string `json:"walletconflicts"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
TimeReceived int64 `json:"timereceived"`
|
||||||
|
Details []GetTransactionDetailsResult `json:"details"`
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoWalletResult models the data returned by the wallet server getinfo
|
||||||
|
// command.
|
||||||
|
type InfoWalletResult struct {
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
ProtocolVersion int32 `json:"protocolversion"`
|
||||||
|
WalletVersion int32 `json:"walletversion"`
|
||||||
|
Balance float64 `json:"balance"`
|
||||||
|
Blocks int32 `json:"blocks"`
|
||||||
|
TimeOffset int64 `json:"timeoffset"`
|
||||||
|
Connections int32 `json:"connections"`
|
||||||
|
Proxy string `json:"proxy"`
|
||||||
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
TestNet bool `json:"testnet"`
|
||||||
|
KeypoolOldest int64 `json:"keypoololdest"`
|
||||||
|
KeypoolSize int32 `json:"keypoolsize"`
|
||||||
|
UnlockedUntil int64 `json:"unlocked_until"`
|
||||||
|
PaytxFee float64 `json:"paytxfee"`
|
||||||
|
RelayFee float64 `json:"relayfee"`
|
||||||
|
Errors string `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTransactionsResult models the data from the listtransactions command.
|
||||||
|
type ListTransactionsResult struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Fee float64 `json:"fee"`
|
||||||
|
Confirmations int64 `json:"confirmations"`
|
||||||
|
Generated bool `json:"generated,omitempty"`
|
||||||
|
BlockHash string `json:"blockhash,omitempty"`
|
||||||
|
BlockIndex int64 `json:"blockindex,omitempty"`
|
||||||
|
BlockTime int64 `json:"blocktime,omitempty"`
|
||||||
|
TxID string `json:"txid"`
|
||||||
|
WalletConflicts []string `json:"walletconflicts"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
TimeReceived int64 `json:"timereceived"`
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
OtherAccount string `json:"otheraccount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReceivedByAccountResult models the data from the listreceivedbyaccount
|
||||||
|
// command.
|
||||||
|
type ListReceivedByAccountResult struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Confirmations uint64 `json:"confirmations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReceivedByAddressResult models the data from the listreceivedbyaddress
|
||||||
|
// command.
|
||||||
|
type ListReceivedByAddressResult struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Confirmations uint64 `json:"confirmations"`
|
||||||
|
TxIDs []string `json:"txids,omitempty"`
|
||||||
|
InvolvesWatchonly bool `json:"involvesWatchonly,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSinceBlockResult models the data from the listsinceblock command.
|
||||||
|
type ListSinceBlockResult struct {
|
||||||
|
Transactions []ListTransactionsResult `json:"transactions"`
|
||||||
|
LastBlock string `json:"lastblock"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUnspentResult models a successful response from the listunspent request.
|
||||||
|
type ListUnspentResult struct {
|
||||||
|
TxID string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Account string `json:"account"`
|
||||||
|
ScriptPubKey string `json:"scriptPubKey"`
|
||||||
|
RedeemScript string `json:"redeemScript,omitempty"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Confirmations int64 `json:"confirmations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignRawTransactionResult models the data from the signrawtransaction
|
||||||
|
// command.
|
||||||
|
type SignRawTransactionResult struct {
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
Complete bool `json:"complete"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAddressWalletResult models the data returned by the wallet server
|
||||||
|
// validateaddress command.
|
||||||
|
type ValidateAddressWalletResult struct {
|
||||||
|
IsValid bool `json:"isvalid"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
IsMine bool `json:"ismine,omitempty"`
|
||||||
|
IsWatchOnly bool `json:"iswatchonly,omitempty"`
|
||||||
|
IsScript bool `json:"isscript,omitempty"`
|
||||||
|
PubKey string `json:"pubkey,omitempty"`
|
||||||
|
IsCompressed bool `json:"iscompressed,omitempty"`
|
||||||
|
Account string `json:"account,omitempty"`
|
||||||
|
Addresses []string `json:"addresses,omitempty"`
|
||||||
|
Hex string `json:"hex,omitempty"`
|
||||||
|
Script string `json:"script,omitempty"`
|
||||||
|
SigsRequired int32 `json:"sigsrequired,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBestBlockResult models the data from the getbestblock command.
|
||||||
|
type GetBestBlockResult struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
}
|
128
btcjson/v2/btcjson/walletsvrwscmds.go
Normal file
128
btcjson/v2/btcjson/walletsvrwscmds.go
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||||
|
// a wallet server, but are only available via websockets.
|
||||||
|
|
||||||
|
// CreateEncryptedWalletCmd defines the createencryptedwallet JSON-RPC command.
|
||||||
|
type CreateEncryptedWalletCmd struct {
|
||||||
|
Passphrase string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateEncryptedWalletCmd returns a new instance which can be used to issue
|
||||||
|
// a createencryptedwallet JSON-RPC command.
|
||||||
|
func NewCreateEncryptedWalletCmd(passphrase string) *CreateEncryptedWalletCmd {
|
||||||
|
return &CreateEncryptedWalletCmd{
|
||||||
|
Passphrase: passphrase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportWatchingWalletCmd defines the exportwatchingwallet JSON-RPC command.
|
||||||
|
type ExportWatchingWalletCmd struct {
|
||||||
|
Account *string `jsonrpcdefault:"\"\""`
|
||||||
|
Download *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExportWatchingWalletCmd returns a new instance which can be used to issue
|
||||||
|
// a exportwatchingwallet JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewExportWatchingWalletCmd(account *string, download *bool) *ExportWatchingWalletCmd {
|
||||||
|
return &ExportWatchingWalletCmd{
|
||||||
|
Account: account,
|
||||||
|
Download: download,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUnconfirmedBalanceCmd defines the getunconfirmedbalance JSON-RPC command.
|
||||||
|
type GetUnconfirmedBalanceCmd struct {
|
||||||
|
Account *string `jsonrpcdefault:"\"\""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetUnconfirmedBalanceCmd returns a new instance which can be used to issue
|
||||||
|
// a getunconfirmedbalance JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewGetUnconfirmedBalanceCmd(account *string) *GetUnconfirmedBalanceCmd {
|
||||||
|
return &GetUnconfirmedBalanceCmd{
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAddressTransactionsCmd defines the listaddresstransactions JSON-RPC
|
||||||
|
// command.
|
||||||
|
type ListAddressTransactionsCmd struct {
|
||||||
|
Addresses []string
|
||||||
|
Account *string `jsonrpcdefault:"\"\""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListAddressTransactionsCmd returns a new instance which can be used to
|
||||||
|
// issue a listaddresstransactions JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewListAddressTransactionsCmd(addresses []string, account *string) *ListAddressTransactionsCmd {
|
||||||
|
return &ListAddressTransactionsCmd{
|
||||||
|
Addresses: addresses,
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAllTransactionsCmd defines the listalltransactions JSON-RPC command.
|
||||||
|
type ListAllTransactionsCmd struct {
|
||||||
|
Account *string `jsonrpcdefault:"\"\""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListAllTransactionsCmd returns a new instance which can be used to issue a
|
||||||
|
// listalltransactions JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewListAllTransactionsCmd(account *string) *ListAllTransactionsCmd {
|
||||||
|
return &ListAllTransactionsCmd{
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoverAddressesCmd defines the recoveraddresses JSON-RPC command.
|
||||||
|
type RecoverAddressesCmd struct {
|
||||||
|
Account string
|
||||||
|
N int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecoverAddressesCmd returns a new instance which can be used to issue a
|
||||||
|
// recoveraddresses JSON-RPC command.
|
||||||
|
func NewRecoverAddressesCmd(account string, n int) *RecoverAddressesCmd {
|
||||||
|
return &RecoverAddressesCmd{
|
||||||
|
Account: account,
|
||||||
|
N: n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletIsLockedCmd defines the walletislocked JSON-RPC command.
|
||||||
|
type WalletIsLockedCmd struct{}
|
||||||
|
|
||||||
|
// NewWalletIsLockedCmd returns a new instance which can be used to issue a
|
||||||
|
// walletislocked JSON-RPC command.
|
||||||
|
func NewWalletIsLockedCmd() *WalletIsLockedCmd {
|
||||||
|
return &WalletIsLockedCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// The commands in this file are only usable with a wallet server via
|
||||||
|
// websockets.
|
||||||
|
flags := UFWalletOnly | UFWebsocketOnly
|
||||||
|
|
||||||
|
MustRegisterCmd("createencryptedwallet", (*CreateEncryptedWalletCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("exportwatchingwallet", (*ExportWatchingWalletCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getunconfirmedbalance", (*GetUnconfirmedBalanceCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listaddresstransactions", (*ListAddressTransactionsCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listalltransactions", (*ListAllTransactionsCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("recoveraddresses", (*RecoverAddressesCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("walletislocked", (*WalletIsLockedCmd)(nil), flags)
|
||||||
|
}
|
259
btcjson/v2/btcjson/walletsvrwscmds_test.go
Normal file
259
btcjson/v2/btcjson/walletsvrwscmds_test.go
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestWalletSvrWsCmds tests all of the wallet server websocket-specific
|
||||||
|
// commands marshal and unmarshal into valid results include handling of
|
||||||
|
// optional fields being omitted in the marshalled command, while optional
|
||||||
|
// fields with defaults have the default assigned on unmarshalled commands.
|
||||||
|
func TestWalletSvrWsCmds(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testID := int(1)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
newCmd func() (interface{}, error)
|
||||||
|
staticCmd func() interface{}
|
||||||
|
marshalled string
|
||||||
|
unmarshalled interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "createencryptedwallet",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("createencryptedwallet", "pass")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewCreateEncryptedWalletCmd("pass")
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"createencryptedwallet","params":["pass"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.CreateEncryptedWalletCmd{Passphrase: "pass"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exportwatchingwallet",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("exportwatchingwallet")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewExportWatchingWalletCmd(nil, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"exportwatchingwallet","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ExportWatchingWalletCmd{
|
||||||
|
Account: btcjson.String(""),
|
||||||
|
Download: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exportwatchingwallet optional1",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("exportwatchingwallet", "acct")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewExportWatchingWalletCmd(btcjson.String("acct"), nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"exportwatchingwallet","params":["acct"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ExportWatchingWalletCmd{
|
||||||
|
Account: btcjson.String("acct"),
|
||||||
|
Download: btcjson.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exportwatchingwallet optional2",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("exportwatchingwallet", "acct", true)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewExportWatchingWalletCmd(btcjson.String("acct"),
|
||||||
|
btcjson.Bool(true))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"exportwatchingwallet","params":["acct",true],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ExportWatchingWalletCmd{
|
||||||
|
Account: btcjson.String("acct"),
|
||||||
|
Download: btcjson.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getunconfirmedbalance",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getunconfirmedbalance")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetUnconfirmedBalanceCmd(nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getunconfirmedbalance","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetUnconfirmedBalanceCmd{
|
||||||
|
Account: btcjson.String(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getunconfirmedbalance optional1",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("getunconfirmedbalance", "acct")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewGetUnconfirmedBalanceCmd(btcjson.String("acct"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"getunconfirmedbalance","params":["acct"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.GetUnconfirmedBalanceCmd{
|
||||||
|
Account: btcjson.String("acct"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "listaddresstransactions",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("listaddresstransactions", `["1Address"]`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewListAddressTransactionsCmd([]string{"1Address"}, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"]],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ListAddressTransactionsCmd{
|
||||||
|
Addresses: []string{"1Address"},
|
||||||
|
Account: btcjson.String(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "listaddresstransactions optional1",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("listaddresstransactions", `["1Address"]`, "acct")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewListAddressTransactionsCmd([]string{"1Address"},
|
||||||
|
btcjson.String("acct"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"],"acct"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ListAddressTransactionsCmd{
|
||||||
|
Addresses: []string{"1Address"},
|
||||||
|
Account: btcjson.String("acct"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "listalltransactions",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("listalltransactions")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewListAllTransactionsCmd(nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"listalltransactions","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ListAllTransactionsCmd{
|
||||||
|
Account: btcjson.String(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "listalltransactions optional",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("listalltransactions", "acct")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewListAllTransactionsCmd(btcjson.String("acct"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"listalltransactions","params":["acct"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.ListAllTransactionsCmd{
|
||||||
|
Account: btcjson.String("acct"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "recoveraddresses",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("recoveraddresses", "acct", 10)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewRecoverAddressesCmd("acct", 10)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"recoveraddresses","params":["acct",10],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.RecoverAddressesCmd{
|
||||||
|
Account: "acct",
|
||||||
|
N: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "walletislocked",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("walletislocked")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewWalletIsLockedCmd()
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"walletislocked","params":[],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.WalletIsLockedCmd{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Marshal the command as created by the new static command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is created without error via the generic
|
||||||
|
// new command creation function.
|
||||||
|
cmd, err := test.newCmd()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||||
|
i, test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the command as created by the generic new command
|
||||||
|
// creation function.
|
||||||
|
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var request btcjson.Request
|
||||||
|
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||||
|
"unmarshalling JSON-RPC request: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||||
|
"- got %s, want %s", i, test.name,
|
||||||
|
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||||
|
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
btcjson/v2/btcjson/walletsvrwsntfns.go
Normal file
95
btcjson/v2/btcjson/walletsvrwsntfns.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// NOTE: This file is intended to house the RPC websocket notifications that are
|
||||||
|
// supported by a wallet server.
|
||||||
|
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AccountBalanceNtfnMethod is the method used for account balance
|
||||||
|
// notifications.
|
||||||
|
AccountBalanceNtfnMethod = "accountbalance"
|
||||||
|
|
||||||
|
// BtcdConnectedNtfnMethod is the method used for notifications when
|
||||||
|
// a wallet server is connected to a chain server.
|
||||||
|
BtcdConnectedNtfnMethod = "btcdconnected"
|
||||||
|
|
||||||
|
// WalletLockStateNtfnMethod is the method used to notify the lock state
|
||||||
|
// of a wallet has changed.
|
||||||
|
WalletLockStateNtfnMethod = "walletlockstate"
|
||||||
|
|
||||||
|
// NewTxNtfnMethod is the method used to notify that a wallet server has
|
||||||
|
// added a new transaction to the transaciton store.
|
||||||
|
NewTxNtfnMethod = "newtx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountBalanceNtfn defines the accountbalance JSON-RPC notification.
|
||||||
|
type AccountBalanceNtfn struct {
|
||||||
|
Account string
|
||||||
|
Balance float64 // In BTC
|
||||||
|
Confirmed bool // Whether Balance is confirmed or unconfirmed.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccountBalanceNtfn returns a new instance which can be used to issue an
|
||||||
|
// accountbalance JSON-RPC notification.
|
||||||
|
func NewAccountBalanceNtfn(account string, balance float64, confirmed bool) *AccountBalanceNtfn {
|
||||||
|
return &AccountBalanceNtfn{
|
||||||
|
Account: account,
|
||||||
|
Balance: balance,
|
||||||
|
Confirmed: confirmed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcdConnectedNtfn defines the btcdconnected JSON-RPC notification.
|
||||||
|
type BtcdConnectedNtfn struct {
|
||||||
|
Connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBtcdConnectedNtfn returns a new instance which can be used to issue a
|
||||||
|
// btcdconnected JSON-RPC notification.
|
||||||
|
func NewBtcdConnectedNtfn(connected bool) *BtcdConnectedNtfn {
|
||||||
|
return &BtcdConnectedNtfn{
|
||||||
|
Connected: connected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletLockStateNtfn defines the walletlockstate JSON-RPC notification.
|
||||||
|
type WalletLockStateNtfn struct {
|
||||||
|
Locked bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWalletLockStateNtfn returns a new instance which can be used to issue a
|
||||||
|
// walletlockstate JSON-RPC notification.
|
||||||
|
func NewWalletLockStateNtfn(locked bool) *WalletLockStateNtfn {
|
||||||
|
return &WalletLockStateNtfn{
|
||||||
|
Locked: locked,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTxNtfn defines the newtx JSON-RPC notification.
|
||||||
|
type NewTxNtfn struct {
|
||||||
|
Account string
|
||||||
|
Details ListTransactionsResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNewTxNtfn returns a new instance which can be used to issue a newtx
|
||||||
|
// JSON-RPC notification.
|
||||||
|
func NewNewTxNtfn(account string, details ListTransactionsResult) *NewTxNtfn {
|
||||||
|
return &NewTxNtfn{
|
||||||
|
Account: account,
|
||||||
|
Details: details,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// The commands in this file are only usable with a wallet server via
|
||||||
|
// websockets and are notifications.
|
||||||
|
flags := UFWalletOnly | UFWebsocketOnly | UFNotification
|
||||||
|
|
||||||
|
MustRegisterCmd(AccountBalanceNtfnMethod, (*AccountBalanceNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(BtcdConnectedNtfnMethod, (*BtcdConnectedNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(WalletLockStateNtfnMethod, (*WalletLockStateNtfn)(nil), flags)
|
||||||
|
MustRegisterCmd(NewTxNtfnMethod, (*NewTxNtfn)(nil), flags)
|
||||||
|
}
|
179
btcjson/v2/btcjson/walletsvrwsntfns_test.go
Normal file
179
btcjson/v2/btcjson/walletsvrwsntfns_test.go
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcjson_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestWalletSvrWsNtfns tests all of the chain server websocket-specific
|
||||||
|
// notifications marshal and unmarshal into valid results include handling of
|
||||||
|
// optional fields being omitted in the marshalled command, while optional
|
||||||
|
// fields with defaults have the default assigned on unmarshalled commands.
|
||||||
|
func TestWalletSvrWsNtfns(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
newNtfn func() (interface{}, error)
|
||||||
|
staticNtfn func() interface{}
|
||||||
|
marshalled string
|
||||||
|
unmarshalled interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "accountbalance",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("accountbalance", "acct", 1.25, true)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
return btcjson.NewAccountBalanceNtfn("acct", 1.25, true)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"accountbalance","params":["acct",1.25,true],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.AccountBalanceNtfn{
|
||||||
|
Account: "acct",
|
||||||
|
Balance: 1.25,
|
||||||
|
Confirmed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "btcdconnected",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("btcdconnected", true)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
return btcjson.NewBtcdConnectedNtfn(true)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"btcdconnected","params":[true],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.BtcdConnectedNtfn{
|
||||||
|
Connected: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "walletlockstate",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("walletlockstate", true)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
return btcjson.NewWalletLockStateNtfn(true)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"walletlockstate","params":[true],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.WalletLockStateNtfn{
|
||||||
|
Locked: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "newtx",
|
||||||
|
newNtfn: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("newtx", "acct", `{"account":"acct","address":"1Address","category":"send","amount":1.5,"fee":0.0001,"confirmations":1,"txid":"456","walletconflicts":[],"time":12345678,"timereceived":12345876,"otheraccount":"otheracct"}`)
|
||||||
|
},
|
||||||
|
staticNtfn: func() interface{} {
|
||||||
|
result := btcjson.ListTransactionsResult{
|
||||||
|
Account: "acct",
|
||||||
|
Address: "1Address",
|
||||||
|
Category: "send",
|
||||||
|
Amount: 1.5,
|
||||||
|
Fee: 0.0001,
|
||||||
|
Confirmations: 1,
|
||||||
|
TxID: "456",
|
||||||
|
WalletConflicts: []string{},
|
||||||
|
Time: 12345678,
|
||||||
|
TimeReceived: 12345876,
|
||||||
|
OtherAccount: "otheracct",
|
||||||
|
}
|
||||||
|
return btcjson.NewNewTxNtfn("acct", result)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"newtx","params":["acct",{"account":"acct","address":"1Address","category":"send","amount":1.5,"fee":0.0001,"confirmations":1,"txid":"456","walletconflicts":[],"time":12345678,"timereceived":12345876,"otheraccount":"otheracct"}],"id":null}`,
|
||||||
|
unmarshalled: &btcjson.NewTxNtfn{
|
||||||
|
Account: "acct",
|
||||||
|
Details: btcjson.ListTransactionsResult{
|
||||||
|
Account: "acct",
|
||||||
|
Address: "1Address",
|
||||||
|
Category: "send",
|
||||||
|
Amount: 1.5,
|
||||||
|
Fee: 0.0001,
|
||||||
|
Confirmations: 1,
|
||||||
|
TxID: "456",
|
||||||
|
WalletConflicts: []string{},
|
||||||
|
Time: 12345678,
|
||||||
|
TimeReceived: 12345876,
|
||||||
|
OtherAccount: "otheracct",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Marshal the notification as created by the new static
|
||||||
|
// creation function. The ID is nil for notifications.
|
||||||
|
marshalled, err := btcjson.MarshalCmd(nil, test.staticNtfn())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the notification is created without error via the
|
||||||
|
// generic new notification creation function.
|
||||||
|
cmd, err := test.newNtfn()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||||
|
i, test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the notification as created by the generic new
|
||||||
|
// notification creation function. The ID is nil for
|
||||||
|
// notifications.
|
||||||
|
marshalled, err = btcjson.MarshalCmd(nil, cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||||
|
"got %s, want %s", i, test.name, marshalled,
|
||||||
|
test.marshalled)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var request btcjson.Request
|
||||||
|
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||||
|
"unmarshalling JSON-RPC request: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
|
test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||||
|
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||||
|
"- got %s, want %s", i, test.name,
|
||||||
|
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||||
|
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue