diff --git a/btcjson/CONTRIBUTORS b/btcjson/CONTRIBUTORS new file mode 100644 index 00000000..b8d98b32 --- /dev/null +++ b/btcjson/CONTRIBUTORS @@ -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 + +John C. Vernaleo +Dave Collins +Owain G. Ainsworth +David Hill +Josh Rickmar +Andreas Metsälä +Francis Lam +Geert-Johan Riemer diff --git a/btcjson/README.md b/btcjson/README.md new file mode 100644 index 00000000..e3662c6d --- /dev/null +++ b/btcjson/README.md @@ -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. diff --git a/btcjson/cmdhelp.go b/btcjson/cmdhelp.go new file mode 100644 index 00000000..5b4a2643 --- /dev/null +++ b/btcjson/cmdhelp.go @@ -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: +=,= +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") +} diff --git a/btcjson/doc.go b/btcjson/doc.go new file mode 100644 index 00000000..bab66104 --- /dev/null +++ b/btcjson/doc.go @@ -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 diff --git a/btcjson/internal_test.go b/btcjson/internal_test.go new file mode 100644 index 00000000..3116ec28 --- /dev/null +++ b/btcjson/internal_test.go @@ -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 +} diff --git a/btcjson/jsonapi.go b/btcjson/jsonapi.go new file mode 100644 index 00000000..7d4693c9 --- /dev/null +++ b/btcjson/jsonapi.go @@ -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 NewCmd 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 NewCmd 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 +} diff --git a/btcjson/jsonapi_test.go b/btcjson/jsonapi_test.go new file mode 100644 index 00000000..05800370 --- /dev/null +++ b/btcjson/jsonapi_test.go @@ -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) + } + } + } +} diff --git a/btcjson/jsoncmd.go b/btcjson/jsoncmd.go new file mode 100644 index 00000000..e1812a22 --- /dev/null +++ b/btcjson/jsoncmd.go @@ -0,0 +1,7283 @@ +// 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" +) + +// ErrTooManyOptArgs describes an error where too many optional +// arguments were passed when creating a new Cmd. +var ErrTooManyOptArgs = errors.New("too many optional arguments") + +// ErrWrongNumberOfParams describes an error where an incorrect number of +// parameters are found in an unmarshalled command. +var ErrWrongNumberOfParams = errors.New("incorrect number of parameters") + +// Cmd is an interface for all Bitcoin JSON API commands to marshal +// and unmarshal as a JSON object. +type Cmd interface { + json.Marshaler + json.Unmarshaler + Id() interface{} + Method() string +} + +// RawCmd is a type for unmarshaling raw commands into before the +// custom command type is set. Other packages may register their +// own RawCmd to Cmd converters by calling RegisterCustomCmd. +type RawCmd struct { + Jsonrpc string `json:"jsonrpc"` + Id interface{} `json:"id"` + Method string `json:"method"` + Params []json.RawMessage `json:"params"` +} + +// NewRawCmd returns a new raw command given the provided id, method, and +// parameters. The parameters are marshalled into a json.RawMessage for the +// Params field of the returned raw command. +func NewRawCmd(id interface{}, method string, params []interface{}) (*RawCmd, error) { + 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 &RawCmd{ + Jsonrpc: "1.0", + Id: id, + Method: method, + Params: rawParams, + }, nil +} + +// RawCmdParser is a function to create a custom Cmd from a RawCmd. +type RawCmdParser func(*RawCmd) (Cmd, error) + +// ReplyParser is a function a custom Cmd can use to unmarshal the results of a +// reply into a concrete struct. +type ReplyParser func(json.RawMessage) (interface{}, error) + +type cmd struct { + parser RawCmdParser + replyParser ReplyParser + helpString string +} + +var customCmds = make(map[string]cmd) + +// RegisterCustomCmd registers a custom RawCmd parsing func, reply parsing func, +// and help text for a non-standard Bitcoin command. +func RegisterCustomCmd(method string, parser RawCmdParser, replyParser ReplyParser, helpString string) { + customCmds[method] = cmd{ + parser: parser, + replyParser: replyParser, + helpString: helpString, + } +} + +// ParseMarshaledCmd parses a raw command and unmarshals as a Cmd. +// Code that reads and handles commands should switch on the type and +// type assert as the particular commands supported by the program. +// +// In all cases where b is a valid JSON-RPC message, and unmarshalling +// succeeds, a non-nil Cmd will always be returned. This even +// includes error cases where parsing the message into a concrete Cmd +// type fails. This behavior allows RPC server code to reply back with +// a detailed error using the Id and Method functions of the Cmd +// interface. +func ParseMarshaledCmd(b []byte) (Cmd, error) { + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return nil, err + } + + // Return a custom command type for recognized + var cmd Cmd + switch r.Method { + case "addmultisigaddress": + cmd = new(AddMultisigAddressCmd) + + case "addnode": + cmd = new(AddNodeCmd) + + case "backupwallet": + cmd = new(BackupWalletCmd) + + case "createmultisig": + cmd = new(CreateMultisigCmd) + + case "createrawtransaction": + cmd = new(CreateRawTransactionCmd) + + case "debuglevel": + cmd = new(DebugLevelCmd) + + case "decoderawtransaction": + cmd = new(DecodeRawTransactionCmd) + + case "decodescript": + cmd = new(DecodeScriptCmd) + + case "dumpprivkey": + cmd = new(DumpPrivKeyCmd) + + case "dumpwallet": + cmd = new(DumpWalletCmd) + + case "encryptwallet": + cmd = new(EncryptWalletCmd) + + case "estimatefee": + cmd = new(EstimateFeeCmd) + + case "estimatepriority": + cmd = new(EstimatePriorityCmd) + + case "getaccount": + cmd = new(GetAccountCmd) + + case "getaccountaddress": + cmd = new(GetAccountAddressCmd) + + case "getaddednodeinfo": + cmd = new(GetAddedNodeInfoCmd) + + case "getaddressesbyaccount": + cmd = new(GetAddressesByAccountCmd) + + case "getbalance": + cmd = new(GetBalanceCmd) + + case "getbestblockhash": + cmd = new(GetBestBlockHashCmd) + + case "getblock": + cmd = new(GetBlockCmd) + + case "getblockchaininfo": + cmd = new(GetBlockChainInfoCmd) + + case "getblockcount": + cmd = new(GetBlockCountCmd) + + case "getblockhash": + cmd = new(GetBlockHashCmd) + + case "getblocktemplate": + cmd = new(GetBlockTemplateCmd) + + case "getconnectioncount": + cmd = new(GetConnectionCountCmd) + + case "getdifficulty": + cmd = new(GetDifficultyCmd) + + case "getgenerate": + cmd = new(GetGenerateCmd) + + case "gethashespersec": + cmd = new(GetHashesPerSecCmd) + + case "getinfo": + cmd = new(GetInfoCmd) + + case "getmininginfo": + cmd = new(GetMiningInfoCmd) + + case "getnettotals": + cmd = new(GetNetTotalsCmd) + + case "getnetworkhashps": + cmd = new(GetNetworkHashPSCmd) + + case "getnetworkinfo": + cmd = new(GetNetworkInfoCmd) + + case "getnewaddress": + cmd = new(GetNewAddressCmd) + + case "getpeerinfo": + cmd = new(GetPeerInfoCmd) + + case "getrawchangeaddress": + cmd = new(GetRawChangeAddressCmd) + + case "getrawmempool": + cmd = new(GetRawMempoolCmd) + + case "getrawtransaction": + cmd = new(GetRawTransactionCmd) + + case "getreceivedbyaccount": + cmd = new(GetReceivedByAccountCmd) + + case "getreceivedbyaddress": + cmd = new(GetReceivedByAddressCmd) + + case "gettransaction": + cmd = new(GetTransactionCmd) + + case "gettxout": + cmd = new(GetTxOutCmd) + + case "gettxoutsetinfo": + cmd = new(GetTxOutSetInfoCmd) + + case "getwork": + cmd = new(GetWorkCmd) + + case "help": + cmd = new(HelpCmd) + + case "importaddress": + cmd = new(ImportAddressCmd) + + case "importpubkey": + cmd = new(ImportPubKeyCmd) + + case "importprivkey": + cmd = new(ImportPrivKeyCmd) + + case "importwallet": + cmd = new(ImportWalletCmd) + + case "invalidateblock": + cmd = new(InvalidateBlockCmd) + + case "keypoolrefill": + cmd = new(KeyPoolRefillCmd) + + case "listaccounts": + cmd = new(ListAccountsCmd) + + case "listaddressgroupings": + cmd = new(ListAddressGroupingsCmd) + + case "listlockunspent": + cmd = new(ListLockUnspentCmd) + + case "listreceivedbyaccount": + cmd = new(ListReceivedByAccountCmd) + + case "listreceivedbyaddress": + cmd = new(ListReceivedByAddressCmd) + + case "listsinceblock": + cmd = new(ListSinceBlockCmd) + + case "listtransactions": + cmd = new(ListTransactionsCmd) + + case "listunspent": + cmd = new(ListUnspentCmd) + + case "lockunspent": + cmd = new(LockUnspentCmd) + + case "move": + cmd = new(MoveCmd) + + case "ping": + cmd = new(PingCmd) + + case "reconsiderblock": + cmd = new(ReconsiderBlockCmd) + + case "searchrawtransactions": + cmd = new(SearchRawTransactionsCmd) + + case "sendfrom": + cmd = new(SendFromCmd) + + case "sendmany": + cmd = new(SendManyCmd) + + case "sendrawtransaction": + cmd = new(SendRawTransactionCmd) + + case "sendtoaddress": + cmd = new(SendToAddressCmd) + + case "setaccount": + cmd = new(SetAccountCmd) + + case "setgenerate": + cmd = new(SetGenerateCmd) + + case "settxfee": + cmd = new(SetTxFeeCmd) + + case "signmessage": + cmd = new(SignMessageCmd) + + case "signrawtransaction": + cmd = new(SignRawTransactionCmd) + + case "stop": + cmd = new(StopCmd) + + case "submitblock": + cmd = new(SubmitBlockCmd) + + case "validateaddress": + cmd = new(ValidateAddressCmd) + + case "verifychain": + cmd = new(VerifyChainCmd) + + case "verifymessage": + cmd = new(VerifyMessageCmd) + + case "walletlock": + cmd = new(WalletLockCmd) + + case "walletpassphrase": + cmd = new(WalletPassphraseCmd) + + case "walletpassphrasechange": + cmd = new(WalletPassphraseChangeCmd) + + default: + // None of the standard Bitcoin RPC methods matched. Try + // registered custom commands. + if c, ok := customCmds[r.Method]; ok { + cmd, err := c.parser(&r) + if err != nil { + cmd = newUnparsableCmd(r.Id, r.Method) + return cmd, err + } + return cmd, nil + } + } + + if cmd == nil { + cmd = newUnparsableCmd(r.Id, r.Method) + return cmd, ErrMethodNotFound + } + + // If we get here we have a cmd that can unmarshal itself. + if err := cmd.UnmarshalJSON(b); err != nil { + cmd = newUnparsableCmd(r.Id, r.Method) + return cmd, err + } + return cmd, nil +} + +// unparsableCmd is a type representing a valid unmarshalled JSON-RPC +// request, but is used for cases where parsing the RPC request into a +// concrete Cmd type failed, either due to an unknown method, or trying +// to parse incorrect arguments for a known method. +type unparsableCmd struct { + id interface{} + method string +} + +// Enforce that unparsableCmd satisifies the Cmd interface. +var _ Cmd = &unparsableCmd{} + +func newUnparsableCmd(id interface{}, method string) *unparsableCmd { + return &unparsableCmd{ + id: id, + method: method, + } +} + +// Id satisifies the Cmd interface by returning the id of the command. +func (cmd *unparsableCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *unparsableCmd) Method() string { + return cmd.method +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *unparsableCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.method, nil) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *unparsableCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + cmd.id = r.Id + cmd.method = r.Method + return nil +} + +// AddMultisigAddressCmd is a type handling custom marshaling and +// unmarshaling of addmultisigaddress JSON RPC commands. +type AddMultisigAddressCmd struct { + id interface{} + NRequired int + Keys []string + Account string +} + +// Enforce that AddMultisigAddress satisifies the Cmd interface. +var _ Cmd = &AddMultisigAddressCmd{} + +// NewAddMultisigAddressCmd creates a new AddMultisigAddressCmd, +// parsing the optional arguments optArgs which may be either empty or an +// optional account name. +func NewAddMultisigAddressCmd(id interface{}, nRequired int, keys []string, + optArgs ...string) (*AddMultisigAddressCmd, error) { + // Optional parameters set to their defaults. + var account string + + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + account = optArgs[0] + } + + return &AddMultisigAddressCmd{ + id: id, + NRequired: nRequired, + Keys: keys, + Account: account, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *AddMultisigAddressCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *AddMultisigAddressCmd) Method() string { + return "addmultisigaddress" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *AddMultisigAddressCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 2, 3) + params[0] = cmd.NRequired + params[1] = cmd.Keys + if cmd.Account != "" { + params = append(params, cmd.Account) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *AddMultisigAddressCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) < 2 || len(r.Params) > 3 { + return ErrWrongNumberOfParams + } + + var nRequired int + if err := json.Unmarshal(r.Params[0], &nRequired); err != nil { + return fmt.Errorf("first parameter 'nrequired' must be an integer: %v", err) + } + + var keys []string + if err := json.Unmarshal(r.Params[1], &keys); err != nil { + return fmt.Errorf("second parameter 'keys' must be an array of strings: %v", err) + } + + var account string + if len(r.Params) > 2 { + if err := json.Unmarshal(r.Params[2], &account); err != nil { + return fmt.Errorf("third optional parameter 'account' must be a string: %v", err) + } + } + newCmd, err := NewAddMultisigAddressCmd(r.Id, nRequired, keys, account) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// AddNodeCmd is a type handling custom marshaling and +// unmarshaling of addmultisigaddress JSON RPC commands. +type AddNodeCmd struct { + id interface{} + Addr string + SubCmd string // One of "add","remove","onetry". TODO(oga) enum? +} + +// Enforce that AddNodeCmd satisifies the Cmd interface. +var _ Cmd = &AddNodeCmd{} + +// NewAddNodeCmd creates a new AddNodeCmd for the given address and subcommand. +func NewAddNodeCmd(id interface{}, addr string, subcmd string) ( + *AddNodeCmd, error) { + + switch subcmd { + case "add": + // fine + case "remove": + // fine + case "onetry": + // fine + default: + return nil, errors.New("invalid subcommand for addnode") + } + + return &AddNodeCmd{ + id: id, + Addr: addr, + SubCmd: subcmd, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *AddNodeCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *AddNodeCmd) Method() string { + return "addnode" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *AddNodeCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Addr, + cmd.SubCmd, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *AddNodeCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 2 { + return ErrWrongNumberOfParams + } + + var addr string + if err := json.Unmarshal(r.Params[0], &addr); err != nil { + return fmt.Errorf("first parameter 'addr' must be a string: %v", err) + } + + var subcmd string + if err := json.Unmarshal(r.Params[1], &subcmd); err != nil { + return fmt.Errorf("second parameter 'subcmd' must be a string: %v", err) + } + + newCmd, err := NewAddNodeCmd(r.Id, addr, subcmd) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// BackupWalletCmd is a type handling custom marshaling and +// unmarshaling of backupwallet JSON RPC commands. +type BackupWalletCmd struct { + id interface{} + Destination string +} + +// Enforce that BackupWalletCmd satisifies the Cmd interface. +var _ Cmd = &BackupWalletCmd{} + +// NewBackupWalletCmd creates a new BackupWalletCmd. +func NewBackupWalletCmd(id interface{}, path string) (*BackupWalletCmd, error) { + + return &BackupWalletCmd{ + id: id, + Destination: path, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *BackupWalletCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *BackupWalletCmd) Method() string { + return "backupwallet" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *BackupWalletCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Destination, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *BackupWalletCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var destination string + if err := json.Unmarshal(r.Params[0], &destination); err != nil { + return fmt.Errorf("first parameter 'destination' must be a string: %v", err) + } + + newCmd, err := NewBackupWalletCmd(r.Id, destination) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// CreateMultisigCmd is a type handling custom marshaling and +// unmarshaling of createmultisig JSON RPC commands. +type CreateMultisigCmd struct { + id interface{} + NRequired int + Keys []string +} + +// Enforce that AddMultisigAddress satisifies the Cmd interface. +var _ Cmd = &CreateMultisigCmd{} + +// NewCreateMultisigCmd creates a new CreateMultisigCmd, +// parsing the optional arguments optArgs. +func NewCreateMultisigCmd(id interface{}, nRequired int, keys []string) (*CreateMultisigCmd, error) { + return &CreateMultisigCmd{ + id: id, + NRequired: nRequired, + Keys: keys, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *CreateMultisigCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *CreateMultisigCmd) Method() string { + return "createmultisig" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *CreateMultisigCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.NRequired, + cmd.Keys, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *CreateMultisigCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 2 { + return ErrWrongNumberOfParams + } + + var nRequired int + if err := json.Unmarshal(r.Params[0], &nRequired); err != nil { + return fmt.Errorf("first parameter 'nrequired' must be an integer: %v", err) + } + + var keys []string + if err := json.Unmarshal(r.Params[1], &keys); err != nil { + return fmt.Errorf("second parameter 'keys' must be an array of strings: %v", err) + } + + newCmd, err := NewCreateMultisigCmd(r.Id, nRequired, keys) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// TransactionInput represents the inputs to a transaction. Specifically a +// transactionsha and output number pair. +type TransactionInput struct { + Txid string `json:"txid"` + Vout uint32 `json:"vout"` +} + +// CreateRawTransactionCmd is a type handling custom marshaling and +// unmarshaling of createrawtransaction JSON RPC commands. +type CreateRawTransactionCmd struct { + id interface{} + Inputs []TransactionInput + Amounts map[string]int64 +} + +// Enforce that CreateRawTransactionCmd satisifies the Cmd interface. +var _ Cmd = &CreateRawTransactionCmd{} + +// NewCreateRawTransactionCmd creates a new CreateRawTransactionCmd. +func NewCreateRawTransactionCmd(id interface{}, inputs []TransactionInput, amounts map[string]int64) (*CreateRawTransactionCmd, error) { + return &CreateRawTransactionCmd{ + id: id, + Inputs: inputs, + Amounts: amounts, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *CreateRawTransactionCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *CreateRawTransactionCmd) Method() string { + return "createrawtransaction" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *CreateRawTransactionCmd) MarshalJSON() ([]byte, error) { + floatAmounts := make(map[string]float64) + for k, v := range cmd.Amounts { + floatAmounts[k] = float64(v) / 1e8 + } + + params := []interface{}{ + cmd.Inputs, + floatAmounts, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *CreateRawTransactionCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 2 { + return ErrWrongNumberOfParams + } + + var inputs []TransactionInput + if err := json.Unmarshal(r.Params[0], &inputs); err != nil { + return fmt.Errorf("first parameter 'inputs' must be a JSON array "+ + "of transaction input JSON objects: %v", err) + } + + var amounts map[string]float64 + if err := json.Unmarshal(r.Params[1], &amounts); err != nil { + return fmt.Errorf("second parameter 'amounts' must be a JSON object: %v", err) + } + + intAmounts := make(map[string]int64, len(amounts)) + for k, v := range amounts { + amount, err := JSONToAmount(v) + if err != nil { + return err + } + intAmounts[k] = amount + } + + newCmd, err := NewCreateRawTransactionCmd(r.Id, inputs, intAmounts) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// DebugLevelCmd is a type handling custom marshaling and unmarshaling of +// debuglevel JSON RPC commands. This command is not a standard Bitcoin +// command. It is an extension for btcd. +type DebugLevelCmd struct { + id interface{} + LevelSpec string +} + +// Enforce that DebugLevelCmd satisifies the Cmd interface. +var _ Cmd = &DebugLevelCmd{} + +// NewDebugLevelCmd creates a new DebugLevelCmd. This command is not a standard +// Bitcoin command. It is an extension for btcd. +func NewDebugLevelCmd(id interface{}, levelSpec string) (*DebugLevelCmd, error) { + return &DebugLevelCmd{ + id: id, + LevelSpec: levelSpec, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *DebugLevelCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *DebugLevelCmd) Method() string { + return "debuglevel" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *DebugLevelCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.LevelSpec, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *DebugLevelCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var levelSpec string + if err := json.Unmarshal(r.Params[0], &levelSpec); err != nil { + return fmt.Errorf("first parameter 'levelspec' must be a string: %v", err) + } + + newCmd, err := NewDebugLevelCmd(r.Id, levelSpec) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// DecodeRawTransactionCmd is a type handling custom marshaling and +// unmarshaling of decoderawtransaction JSON RPC commands. +type DecodeRawTransactionCmd struct { + id interface{} + HexTx string +} + +// Enforce that DecodeRawTransactionCmd satisifies the Cmd interface. +var _ Cmd = &DecodeRawTransactionCmd{} + +// NewDecodeRawTransactionCmd creates a new DecodeRawTransactionCmd. +func NewDecodeRawTransactionCmd(id interface{}, hextx string) (*DecodeRawTransactionCmd, error) { + return &DecodeRawTransactionCmd{ + id: id, + HexTx: hextx, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *DecodeRawTransactionCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *DecodeRawTransactionCmd) Method() string { + return "decoderawtransaction" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *DecodeRawTransactionCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.HexTx, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *DecodeRawTransactionCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var hextx string + if err := json.Unmarshal(r.Params[0], &hextx); err != nil { + return fmt.Errorf("first parameter 'hextx' must be a string: %v", err) + } + + newCmd, err := NewDecodeRawTransactionCmd(r.Id, hextx) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// DecodeScriptCmd is a type handling custom marshaling and +// unmarshaling of decodescript JSON RPC commands. +type DecodeScriptCmd struct { + id interface{} + HexScript string +} + +// Enforce that DecodeScriptCmd satisifies the Cmd interface. +var _ Cmd = &DecodeScriptCmd{} + +// NewDecodeScriptCmd creates a new DecodeScriptCmd. +func NewDecodeScriptCmd(id interface{}, hexscript string) (*DecodeScriptCmd, error) { + return &DecodeScriptCmd{ + id: id, + HexScript: hexscript, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *DecodeScriptCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *DecodeScriptCmd) Method() string { + return "decodescript" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *DecodeScriptCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.HexScript, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *DecodeScriptCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var hexscript string + if err := json.Unmarshal(r.Params[0], &hexscript); err != nil { + return fmt.Errorf("first parameter 'hexscript' must be a string: %v", err) + } + + newCmd, err := NewDecodeScriptCmd(r.Id, hexscript) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// DumpPrivKeyCmd is a type handling custom marshaling and +// unmarshaling of dumpprivkey JSON RPC commands. +type DumpPrivKeyCmd struct { + id interface{} + Address string +} + +// Enforce that DumpPrivKeyCmd satisifies the Cmd interface. +var _ Cmd = &DumpPrivKeyCmd{} + +// NewDumpPrivKeyCmd creates a new DumpPrivkeyCmd. +func NewDumpPrivKeyCmd(id interface{}, address string) (*DumpPrivKeyCmd, error) { + return &DumpPrivKeyCmd{ + id: id, + Address: address, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *DumpPrivKeyCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *DumpPrivKeyCmd) Method() string { + return "dumpprivkey" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *DumpPrivKeyCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Address, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *DumpPrivKeyCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + newCmd, err := NewDumpPrivKeyCmd(r.Id, address) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// DumpWalletCmd is a type handling custom marshaling and +// unmarshaling of dumpwallet JSON RPC commands. +type DumpWalletCmd struct { + id interface{} + Filename string +} + +// Enforce that DumpWalletCmd satisifies the Cmd interface. +var _ Cmd = &DumpWalletCmd{} + +// NewDumpWalletCmd creates a new DumpPrivkeyCmd. +func NewDumpWalletCmd(id interface{}, filename string) (*DumpWalletCmd, error) { + return &DumpWalletCmd{ + id: id, + Filename: filename, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *DumpWalletCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *DumpWalletCmd) Method() string { + return "dumpwallet" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *DumpWalletCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Filename, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *DumpWalletCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var filename string + if err := json.Unmarshal(r.Params[0], &filename); err != nil { + return fmt.Errorf("first parameter 'filename' must be a string: %v", err) + } + + newCmd, err := NewDumpWalletCmd(r.Id, filename) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// EncryptWalletCmd is a type handling custom marshaling and +// unmarshaling of encryptwallet JSON RPC commands. +type EncryptWalletCmd struct { + id interface{} + Passphrase string +} + +// Enforce that EncryptWalletCmd satisifies the Cmd interface. +var _ Cmd = &EncryptWalletCmd{} + +// NewEncryptWalletCmd creates a new EncryptWalletCmd. +func NewEncryptWalletCmd(id interface{}, passphrase string) (*EncryptWalletCmd, error) { + return &EncryptWalletCmd{ + id: id, + Passphrase: passphrase, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *EncryptWalletCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *EncryptWalletCmd) Method() string { + return "encryptwallet" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *EncryptWalletCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Passphrase, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *EncryptWalletCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var passphrase string + if err := json.Unmarshal(r.Params[0], &passphrase); err != nil { + return fmt.Errorf("first parameter 'passphrase' must be a string: %v", err) + } + + newCmd, err := NewEncryptWalletCmd(r.Id, passphrase) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// EstimateFeeCmd is a type handling custom marshaling and +// unmarshaling of estimatefee JSON RPC commands. +type EstimateFeeCmd struct { + id interface{} + NumBlocks int64 +} + +// Enforce that EstimateFeeCmd satisifies the Cmd interface. +var _ Cmd = &EstimateFeeCmd{} + +// NewEstimateFeeCmd creates a new EstimateFeeCmd. +func NewEstimateFeeCmd(id interface{}, numblocks int64) (*EstimateFeeCmd, error) { + return &EstimateFeeCmd{ + id: id, + NumBlocks: numblocks, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *EstimateFeeCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *EstimateFeeCmd) Method() string { + return "estimatefee" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *EstimateFeeCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.NumBlocks, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *EstimateFeeCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var numblocks int64 + if err := json.Unmarshal(r.Params[0], &numblocks); err != nil { + return fmt.Errorf("first parameter 'numblocks' must be an integer: %v", err) + } + + newCmd, err := NewEstimateFeeCmd(r.Id, numblocks) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// EstimatePriorityCmd is a type handling custom marshaling and +// unmarshaling of estimatepriority JSON RPC commands. +type EstimatePriorityCmd struct { + id interface{} + NumBlocks int64 +} + +// Enforce that EstimatePriorityCmd satisifies the Cmd interface. +var _ Cmd = &EstimatePriorityCmd{} + +// NewEstimatePriorityCmd creates a new EstimatePriorityCmd. +func NewEstimatePriorityCmd(id interface{}, numblocks int64) (*EstimatePriorityCmd, error) { + return &EstimatePriorityCmd{ + id: id, + NumBlocks: numblocks, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *EstimatePriorityCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *EstimatePriorityCmd) Method() string { + return "estimatepriority" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *EstimatePriorityCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.NumBlocks, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *EstimatePriorityCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var numblocks int64 + if err := json.Unmarshal(r.Params[0], &numblocks); err != nil { + return fmt.Errorf("first parameter 'numblocks' must be an integer: %v", err) + } + + newCmd, err := NewEstimatePriorityCmd(r.Id, numblocks) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetAccountCmd is a type handling custom marshaling and +// unmarshaling of getaccount JSON RPC commands. +type GetAccountCmd struct { + id interface{} + Address string +} + +// Enforce that GetAccountCmd satisifies the Cmd interface. +var _ Cmd = &GetAccountCmd{} + +// NewGetAccountCmd creates a new GetAccountCmd. +func NewGetAccountCmd(id interface{}, address string) (*GetAccountCmd, error) { + return &GetAccountCmd{ + id: id, + Address: address, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetAccountCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetAccountCmd) Method() string { + return "getaccount" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetAccountCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Address, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetAccountCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + newCmd, err := NewGetAccountCmd(r.Id, address) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetAccountAddressCmd is a type handling custom marshaling and +// unmarshaling of getaccountaddress JSON RPC commands. +type GetAccountAddressCmd struct { + id interface{} + Account string +} + +// Enforce that GetAccountAddressCmd satisifies the Cmd interface. +var _ Cmd = &GetAccountAddressCmd{} + +// NewGetAccountAddressCmd creates a new GetAccountAddressCmd. +func NewGetAccountAddressCmd(id interface{}, account string) (*GetAccountAddressCmd, error) { + return &GetAccountAddressCmd{ + id: id, + Account: account, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetAccountAddressCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetAccountAddressCmd) Method() string { + return "getaccountaddress" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetAccountAddressCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Account, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetAccountAddressCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + var account string + if err := json.Unmarshal(r.Params[0], &account); err != nil { + return fmt.Errorf("first parameter 'account' must be a string: %v", err) + } + + newCmd, err := NewGetAccountAddressCmd(r.Id, account) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetAddedNodeInfoCmd is a type handling custom marshaling and +// unmarshaling of getaddednodeinfo JSON RPC commands. +type GetAddedNodeInfoCmd struct { + id interface{} + Dns bool + Node string +} + +// Enforce that GetAddedNodeInfoCmd satisifies the Cmd interface. +var _ Cmd = &GetAddedNodeInfoCmd{} + +// NewGetAddedNodeInfoCmd creates a new GetAddedNodeInfoCmd. Optionally the +// node to be queried may be provided. More than one optonal argument is an +// error. +func NewGetAddedNodeInfoCmd(id interface{}, dns bool, optArgs ...string) (*GetAddedNodeInfoCmd, error) { + var node string + + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + node = optArgs[0] + } + return &GetAddedNodeInfoCmd{ + id: id, + Dns: dns, + Node: node, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetAddedNodeInfoCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetAddedNodeInfoCmd) Method() string { + return "getaddednodeinfo" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetAddedNodeInfoCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Dns, + } + + if cmd.Node != "" { + params = append(params, cmd.Node) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetAddedNodeInfoCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) < 1 || len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + var dns bool + if err := json.Unmarshal(r.Params[0], &dns); err != nil { + return fmt.Errorf("first parameter 'dns' must be a bool: %v", err) + } + + var node string + if len(r.Params) > 1 { + if err := json.Unmarshal(r.Params[1], &node); err != nil { + return fmt.Errorf("second optional parameter 'node' must be a string: %v", err) + } + } + + newCmd, err := NewGetAddedNodeInfoCmd(r.Id, dns, node) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetAddressesByAccountCmd is a type handling custom marshaling and +// unmarshaling of getaddressesbyaccount JSON RPC commands. +type GetAddressesByAccountCmd struct { + id interface{} + Account string +} + +// Enforce that GetAddressesByAccountCmd satisifies the Cmd interface. +var _ Cmd = &GetAddressesByAccountCmd{} + +// NewGetAddressesByAccountCmd creates a new GetAddressesByAccountCmd. +func NewGetAddressesByAccountCmd(id interface{}, account string) (*GetAddressesByAccountCmd, error) { + return &GetAddressesByAccountCmd{ + id: id, + Account: account, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetAddressesByAccountCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetAddressesByAccountCmd) Method() string { + return "getaddressesbyaccount" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetAddressesByAccountCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Account, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetAddressesByAccountCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var account string + if err := json.Unmarshal(r.Params[0], &account); err != nil { + return fmt.Errorf("first parameter 'account' must be a string: %v", err) + } + + newCmd, err := NewGetAddressesByAccountCmd(r.Id, account) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetBalanceCmd is a type handling custom marshaling and +// unmarshaling of getbalance JSON RPC commands. +type GetBalanceCmd struct { + id interface{} + Account *string + MinConf int +} + +// Enforce that GetBalanceCmd satisifies the Cmd interface. +var _ Cmd = &GetBalanceCmd{} + +// NewGetBalanceCmd creates a new GetBalanceCmd. Optionally a string for account +// and an int for minconf may be provided as arguments. +func NewGetBalanceCmd(id interface{}, optArgs ...interface{}) (*GetBalanceCmd, error) { + var account *string + var minconf = 1 + + if len(optArgs) > 2 { + return nil, ErrWrongNumberOfParams + } + if len(optArgs) > 0 { + a, ok := optArgs[0].(string) + if !ok { + return nil, errors.New("first optional argument account is not a string") + } + account = &a + } + + if len(optArgs) > 1 { + m, ok := optArgs[1].(int) + if !ok { + return nil, errors.New("second optional argument minconf is not a int") + } + minconf = m + } + + return &GetBalanceCmd{ + id: id, + Account: account, + MinConf: minconf, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetBalanceCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetBalanceCmd) Method() string { + return "getbalance" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetBalanceCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 2) + if cmd.Account != nil { + params = append(params, cmd.Account) + } + if cmd.MinConf != 1 { + params = append(params, cmd.MinConf) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetBalanceCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + optArgs := make([]interface{}, 0, 2) + if len(r.Params) > 0 { + var account string + if err := json.Unmarshal(r.Params[0], &account); err != nil { + return fmt.Errorf("first optional parameter 'account' must be a string: %v", err) + } + optArgs = append(optArgs, account) + } + + if len(r.Params) > 1 { + var minconf int + if err := json.Unmarshal(r.Params[1], &minconf); err != nil { + return fmt.Errorf("second optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + + newCmd, err := NewGetBalanceCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetBestBlockHashCmd is a type handling custom marshaling and +// unmarshaling of getbestblockhash JSON RPC commands. +type GetBestBlockHashCmd struct { + id interface{} +} + +// Enforce that GetBestBlockHashCmd satisifies the Cmd interface. +var _ Cmd = &GetBestBlockHashCmd{} + +// NewGetBestBlockHashCmd creates a new GetBestBlockHashCmd. +func NewGetBestBlockHashCmd(id interface{}) (*GetBestBlockHashCmd, error) { + return &GetBestBlockHashCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetBestBlockHashCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetBestBlockHashCmd) Method() string { + return "getbestblockhash" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetBestBlockHashCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetBestBlockHashCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetBestBlockHashCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetBlockCmd is a type handling custom marshaling and +// unmarshaling of getblock JSON RPC commands. +type GetBlockCmd struct { + id interface{} + Hash string + Verbose bool + VerboseTx bool +} + +// Enforce that GetBlockCmd satisifies the Cmd interface. +var _ Cmd = &GetBlockCmd{} + +// NewGetBlockCmd creates a new GetBlockCmd. +func NewGetBlockCmd(id interface{}, hash string, optArgs ...bool) (*GetBlockCmd, error) { + // default verbose is set to true to match old behavior + verbose, verboseTx := true, false + + optArgsLen := len(optArgs) + if optArgsLen > 0 { + if optArgsLen > 2 { + return nil, ErrTooManyOptArgs + } + verbose = optArgs[0] + if optArgsLen > 1 { + verboseTx = optArgs[1] + + if !verbose && verboseTx { + return nil, ErrInvalidParams + } + } + } + + return &GetBlockCmd{ + id: id, + Hash: hash, + Verbose: verbose, + VerboseTx: verboseTx, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetBlockCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetBlockCmd) Method() string { + return "getblock" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetBlockCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 3) + params[0] = cmd.Hash + if !cmd.Verbose { + // set optional verbose argument to false + params = append(params, false) + } else if cmd.VerboseTx { + // set optional verbose argument to true + params = append(params, true) + // set optional verboseTx argument to true + params = append(params, true) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetBlockCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 3 || len(r.Params) < 1 { + return ErrWrongNumberOfParams + } + + var hash string + if err := json.Unmarshal(r.Params[0], &hash); err != nil { + return fmt.Errorf("first parameter 'hash' must be a string: %v", err) + } + + optArgs := make([]bool, 0, 2) + if len(r.Params) > 1 { + var verbose bool + if err := json.Unmarshal(r.Params[1], &verbose); err != nil { + return fmt.Errorf("second optional parameter 'verbose' must be a bool: %v", err) + } + optArgs = append(optArgs, verbose) + } + if len(r.Params) > 2 { + var verboseTx bool + if err := json.Unmarshal(r.Params[2], &verboseTx); err != nil { + return fmt.Errorf("third optional parameter 'verboseTx' must be a bool: %v", err) + } + optArgs = append(optArgs, verboseTx) + } + + newCmd, err := NewGetBlockCmd(r.Id, hash, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetBlockChainInfoCmd is a type handling custom marshaling and +// unmarshaling of getblockchaininfo JSON RPC commands. +type GetBlockChainInfoCmd struct { + id interface{} +} + +// Enforce that GetBlockChainInfoCmd satisifies the Cmd interface. +var _ Cmd = &GetBlockChainInfoCmd{} + +// NewGetBlockChainInfoCmd creates a new GetBlockChainInfoCmd. +func NewGetBlockChainInfoCmd(id interface{}) (*GetBlockChainInfoCmd, error) { + return &GetBlockChainInfoCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetBlockChainInfoCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetBlockChainInfoCmd) Method() string { + return "getblockchaininfo" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetBlockChainInfoCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetBlockChainInfoCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetBlockChainInfoCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetBlockCountCmd is a type handling custom marshaling and +// unmarshaling of getblockcount JSON RPC commands. +type GetBlockCountCmd struct { + id interface{} +} + +// Enforce that GetBlockCountCmd satisifies the Cmd interface. +var _ Cmd = &GetBlockCountCmd{} + +// NewGetBlockCountCmd creates a new GetBlockCountCmd. +func NewGetBlockCountCmd(id interface{}) (*GetBlockCountCmd, error) { + return &GetBlockCountCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetBlockCountCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetBlockCountCmd) Method() string { + return "getblockcount" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetBlockCountCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetBlockCountCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetBlockCountCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetBlockHashCmd is a type handling custom marshaling and +// unmarshaling of getblockhash JSON RPC commands. +type GetBlockHashCmd struct { + id interface{} + Index int64 +} + +// Enforce that GetBlockHashCmd satisifies the Cmd interface. +var _ Cmd = &GetBlockHashCmd{} + +// NewGetBlockHashCmd creates a new GetBlockHashCmd. +func NewGetBlockHashCmd(id interface{}, index int64) (*GetBlockHashCmd, error) { + return &GetBlockHashCmd{ + id: id, + Index: index, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetBlockHashCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetBlockHashCmd) Method() string { + return "getblockhash" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetBlockHashCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Index, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetBlockHashCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var index int64 + if err := json.Unmarshal(r.Params[0], &index); err != nil { + return fmt.Errorf("first parameter 'index' must be an integer: %v", err) + } + + newCmd, err := NewGetBlockHashCmd(r.Id, index) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// 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"` +} + +// isFloatInt64 returns whether the passed float64 is a whole number that safely +// fits into a 64-bit integer. +func isFloatInt64(a float64) bool { + return a == float64(int64(a)) +} + +// GetBlockTemplateCmd is a type handling custom marshaling and +// unmarshaling of getblocktemplate JSON RPC commands. +type GetBlockTemplateCmd struct { + id interface{} + Request *TemplateRequest +} + +// Enforce that GetBlockTemplateCmd satisifies the Cmd interface. +var _ Cmd = &GetBlockTemplateCmd{} + +// NewGetBlockTemplateCmd creates a new GetBlockTemplateCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewGetBlockTemplateCmd(id interface{}, optArgs ...*TemplateRequest) (*GetBlockTemplateCmd, error) { + var request *TemplateRequest + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + request = optArgs[0] + switch val := request.SigOpLimit.(type) { + case nil: + case bool: + case int64: + case float64: + if !isFloatInt64(val) { + return nil, errors.New("the sigoplimit field " + + "must be unspecified, a boolean, or a " + + "64-bit integer") + } + request.SigOpLimit = int64(val) + default: + return nil, errors.New("the sigoplimit field " + + "must be unspecified, a boolean, or a 64-bit " + + "integer") + } + switch val := request.SizeLimit.(type) { + case nil: + case bool: + case int64: + case float64: + if !isFloatInt64(val) { + return nil, errors.New("the sizelimit field " + + "must be unspecified, a boolean, or a " + + "64-bit integer") + } + request.SizeLimit = int64(val) + default: + return nil, errors.New("the sizelimit field " + + "must be unspecified, a boolean, or a 64-bit " + + "integer") + } + } + return &GetBlockTemplateCmd{ + id: id, + Request: request, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetBlockTemplateCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetBlockTemplateCmd) Method() string { + return "getblocktemplate" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetBlockTemplateCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 1) + if cmd.Request != nil { + params = append(params, cmd.Request) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetBlockTemplateCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 1 { + return ErrWrongNumberOfParams + } + + optArgs := make([]*TemplateRequest, 0, 1) + if len(r.Params) > 0 { + var template TemplateRequest + if err := json.Unmarshal(r.Params[0], &template); err != nil { + return fmt.Errorf("first optional parameter 'template' must be a template request JSON object: %v", err) + } + optArgs = append(optArgs, &template) + } + + newCmd, err := NewGetBlockTemplateCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetConnectionCountCmd is a type handling custom marshaling and +// unmarshaling of getconnectioncount JSON RPC commands. +type GetConnectionCountCmd struct { + id interface{} +} + +// Enforce that GetConnectionCountCmd satisifies the Cmd interface. +var _ Cmd = &GetConnectionCountCmd{} + +// NewGetConnectionCountCmd creates a new GetConnectionCountCmd. +func NewGetConnectionCountCmd(id interface{}) (*GetConnectionCountCmd, error) { + return &GetConnectionCountCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetConnectionCountCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetConnectionCountCmd) Method() string { + return "getconnectioncount" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetConnectionCountCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetConnectionCountCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetConnectionCountCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetDifficultyCmd is a type handling custom marshaling and +// unmarshaling of getdifficulty JSON RPC commands. +type GetDifficultyCmd struct { + id interface{} +} + +// Enforce that GetDifficultyCmd satisifies the Cmd interface. +var _ Cmd = &GetDifficultyCmd{} + +// NewGetDifficultyCmd creates a new GetDifficultyCmd. +func NewGetDifficultyCmd(id interface{}) (*GetDifficultyCmd, error) { + return &GetDifficultyCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetDifficultyCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetDifficultyCmd) Method() string { + return "getdifficulty" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetDifficultyCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetDifficultyCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetDifficultyCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetGenerateCmd is a type handling custom marshaling and +// unmarshaling of getgenerate JSON RPC commands. +type GetGenerateCmd struct { + id interface{} +} + +// Enforce that GetGenerateCmd satisifies the Cmd interface. +var _ Cmd = &GetGenerateCmd{} + +// NewGetGenerateCmd creates a new GetGenerateCmd. +func NewGetGenerateCmd(id interface{}) (*GetGenerateCmd, error) { + return &GetGenerateCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetGenerateCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetGenerateCmd) Method() string { + return "getgenerate" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetGenerateCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetGenerateCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetGenerateCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetHashesPerSecCmd is a type handling custom marshaling and +// unmarshaling of gethashespersec JSON RPC commands. +type GetHashesPerSecCmd struct { + id interface{} +} + +// Enforce that GetHashesPerSecCmd satisifies the Cmd interface. +var _ Cmd = &GetHashesPerSecCmd{} + +// NewGetHashesPerSecCmd creates a new GetHashesPerSecCmd. +func NewGetHashesPerSecCmd(id interface{}) (*GetHashesPerSecCmd, error) { + return &GetHashesPerSecCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetHashesPerSecCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetHashesPerSecCmd) Method() string { + return "gethashespersec" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetHashesPerSecCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetHashesPerSecCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetHashesPerSecCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetInfoCmd is a type handling custom marshaling and +// unmarshaling of getinfo JSON RPC commands. +type GetInfoCmd struct { + id interface{} +} + +// Enforce that GetInfoCmd satisifies the Cmd interface. +var _ Cmd = &GetInfoCmd{} + +// NewGetInfoCmd creates a new GetInfoCmd. +func NewGetInfoCmd(id interface{}) (*GetInfoCmd, error) { + return &GetInfoCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetInfoCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetInfoCmd) Method() string { + return "getinfo" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetInfoCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetInfoCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetInfoCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetMiningInfoCmd is a type handling custom marshaling and +// unmarshaling of getmininginfo JSON RPC commands. +type GetMiningInfoCmd struct { + id interface{} +} + +// Enforce that GetMiningInfoCmd satisifies the Cmd interface. +var _ Cmd = &GetMiningInfoCmd{} + +// NewGetMiningInfoCmd creates a new GetMiningInfoCmd. +func NewGetMiningInfoCmd(id interface{}) (*GetMiningInfoCmd, error) { + return &GetMiningInfoCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetMiningInfoCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetMiningInfoCmd) Method() string { + return "getmininginfo" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetMiningInfoCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetMiningInfoCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetMiningInfoCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetNetworkInfoCmd is a type handling custom marshaling and +// unmarshaling of getnetworkinfo JSON RPC commands. +type GetNetworkInfoCmd struct { + id interface{} +} + +// Enforce that GetNetworkInfoCmd satisifies the Cmd interface. +var _ Cmd = &GetNetworkInfoCmd{} + +// NewGetNetworkInfoCmd creates a new GetNetworkInfoCmd. +func NewGetNetworkInfoCmd(id interface{}) (*GetNetworkInfoCmd, error) { + return &GetNetworkInfoCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetNetworkInfoCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetNetworkInfoCmd) Method() string { + return "getnetworkinfo" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetNetworkInfoCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetNetworkInfoCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetNetworkInfoCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetNetTotalsCmd is a type handling custom marshaling and +// unmarshaling of getnettotals JSON RPC commands. +type GetNetTotalsCmd struct { + id interface{} +} + +// Enforce that GetNetTotalsCmd satisifies the Cmd interface. +var _ Cmd = &GetNetTotalsCmd{} + +// NewGetNetTotalsCmd creates a new GetNetTotalsCmd. +func NewGetNetTotalsCmd(id interface{}) (*GetNetTotalsCmd, error) { + return &GetNetTotalsCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetNetTotalsCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetNetTotalsCmd) Method() string { + return "getnettotals" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetNetTotalsCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetNetTotalsCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetNetTotalsCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetNetworkHashPSCmd is a type handling custom marshaling and +// unmarshaling of getnetworkhashps JSON RPC commands. +type GetNetworkHashPSCmd struct { + id interface{} + Blocks int + Height int +} + +// Enforce that GetNetworkHashPSCmd satisifies the Cmd interface. +var _ Cmd = &GetNetworkHashPSCmd{} + +// NewGetNetworkHashPSCmd creates a new GetNetworkHashPSCmd. +func NewGetNetworkHashPSCmd(id interface{}, optArgs ...int) (*GetNetworkHashPSCmd, error) { + blocks := 120 + height := -1 + + if len(optArgs) > 0 { + if len(optArgs) > 2 { + return nil, ErrTooManyOptArgs + } + + blocks = optArgs[0] + + if len(optArgs) > 1 { + height = optArgs[1] + } + } + return &GetNetworkHashPSCmd{ + id: id, + Blocks: blocks, + Height: height, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetNetworkHashPSCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetNetworkHashPSCmd) Method() string { + return "getnetworkhashps" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetNetworkHashPSCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 2) + if cmd.Blocks != 120 || cmd.Height != -1 { + params = append(params, cmd.Blocks) + } + if cmd.Height != -1 { + params = append(params, cmd.Height) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetNetworkHashPSCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + optArgs := make([]int, 0, 2) + if len(r.Params) > 0 { + var blocks int + if err := json.Unmarshal(r.Params[0], &blocks); err != nil { + return fmt.Errorf("first optional parameter 'blocks' must be an integer: %v", err) + } + optArgs = append(optArgs, blocks) + } + + if len(r.Params) > 1 { + var height int + if err := json.Unmarshal(r.Params[1], &height); err != nil { + return fmt.Errorf("second optional parameter 'height' must be an integer: %v", err) + } + optArgs = append(optArgs, height) + } + newCmd, err := NewGetNetworkHashPSCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetNewAddressCmd is a type handling custom marshaling and +// unmarshaling of getnewaddress JSON RPC commands. +type GetNewAddressCmd struct { + id interface{} + Account string +} + +// Enforce that GetNewAddressCmd satisifies the Cmd interface. +var _ Cmd = &GetNewAddressCmd{} + +// NewGetNewAddressCmd creates a new GetNewAddressCmd. +func NewGetNewAddressCmd(id interface{}, optArgs ...string) (*GetNewAddressCmd, error) { + var account string + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + account = optArgs[0] + + } + return &GetNewAddressCmd{ + id: id, + Account: account, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetNewAddressCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetNewAddressCmd) Method() string { + return "getnewaddress" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetNewAddressCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 1) + if cmd.Account != "" { + params = append(params, cmd.Account) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetNewAddressCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 1 { + return ErrWrongNumberOfParams + } + + optArgs := make([]string, 0, 1) + if len(r.Params) > 0 { + var account string + if err := json.Unmarshal(r.Params[0], &account); err != nil { + return fmt.Errorf("first optional parameter 'account' must be a string: %v", err) + } + optArgs = append(optArgs, account) + } + + newCmd, err := NewGetNewAddressCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetPeerInfoCmd is a type handling custom marshaling and +// unmarshaling of getpeerinfo JSON RPC commands. +type GetPeerInfoCmd struct { + id interface{} +} + +// Enforce that GetPeerInfoCmd satisifies the Cmd interface. +var _ Cmd = &GetPeerInfoCmd{} + +// NewGetPeerInfoCmd creates a new GetPeerInfoCmd. +func NewGetPeerInfoCmd(id interface{}) (*GetPeerInfoCmd, error) { + return &GetPeerInfoCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetPeerInfoCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetPeerInfoCmd) Method() string { + return "getpeerinfo" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetPeerInfoCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetPeerInfoCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetPeerInfoCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetRawChangeAddressCmd is a type handling custom marshaling and +// unmarshaling of getrawchangeaddress JSON RPC commands. +type GetRawChangeAddressCmd struct { + id interface{} + Account string +} + +// Enforce that GetRawChangeAddressCmd satisifies the Cmd interface. +var _ Cmd = &GetRawChangeAddressCmd{} + +// NewGetRawChangeAddressCmd creates a new GetRawChangeAddressCmd. +func NewGetRawChangeAddressCmd(id interface{}, optArgs ...string) (*GetRawChangeAddressCmd, error) { + var account string + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + account = optArgs[0] + + } + return &GetRawChangeAddressCmd{ + id: id, + Account: account, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetRawChangeAddressCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetRawChangeAddressCmd) Method() string { + return "getrawchangeaddress" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetRawChangeAddressCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 1) + if cmd.Account != "" { + params = append(params, cmd.Account) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetRawChangeAddressCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 1 { + return ErrWrongNumberOfParams + } + + optArgs := make([]string, 0, 1) + if len(r.Params) > 0 { + var account string + if err := json.Unmarshal(r.Params[0], &account); err != nil { + return fmt.Errorf("first optional parameter 'account' must be a string: %v", err) + } + optArgs = append(optArgs, account) + } + + newCmd, err := NewGetRawChangeAddressCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetRawMempoolCmd is a type handling custom marshaling and +// unmarshaling of getrawmempool JSON RPC commands. +type GetRawMempoolCmd struct { + id interface{} + Verbose bool +} + +// Enforce that GetRawMempoolCmd satisifies the Cmd interface. +var _ Cmd = &GetRawMempoolCmd{} + +// NewGetRawMempoolCmd creates a new GetRawMempoolCmd. +func NewGetRawMempoolCmd(id interface{}, optArgs ...bool) (*GetRawMempoolCmd, error) { + verbose := false + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + verbose = optArgs[0] + } + return &GetRawMempoolCmd{ + id: id, + Verbose: verbose, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetRawMempoolCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetRawMempoolCmd) Method() string { + return "getrawmempool" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetRawMempoolCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 1) + if cmd.Verbose { + params = append(params, cmd.Verbose) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetRawMempoolCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 1 { + return ErrWrongNumberOfParams + } + + optArgs := make([]bool, 0, 1) + if len(r.Params) > 0 { + var verbose bool + if err := json.Unmarshal(r.Params[0], &verbose); err != nil { + return fmt.Errorf("first optional parameter 'verbose' must be a bool: %v", err) + } + optArgs = append(optArgs, verbose) + } + + newCmd, err := NewGetRawMempoolCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetRawTransactionCmd is a type handling custom marshaling and +// unmarshaling of getrawtransaction JSON RPC commands. +type GetRawTransactionCmd struct { + id interface{} + Txid string + Verbose int +} + +// Enforce that GetRawTransactionCmd satisifies the Cmd interface. +var _ Cmd = &GetRawTransactionCmd{} + +// NewGetRawTransactionCmd creates a new GetRawTransactionCmd. +func NewGetRawTransactionCmd(id interface{}, txid string, optArgs ...int) (*GetRawTransactionCmd, error) { + var verbose int + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + verbose = optArgs[0] + + } + return &GetRawTransactionCmd{ + id: id, + Txid: txid, + Verbose: verbose, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetRawTransactionCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetRawTransactionCmd) Method() string { + return "getrawtransaction" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetRawTransactionCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 2) + params[0] = cmd.Txid + if cmd.Verbose != 0 { + params = append(params, cmd.Verbose) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetRawTransactionCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 || len(r.Params) < 1 { + return ErrWrongNumberOfParams + } + + var txid string + if err := json.Unmarshal(r.Params[0], &txid); err != nil { + return fmt.Errorf("first parameter 'txid' must be a string: %v", err) + } + + optArgs := make([]int, 0, 1) + if len(r.Params) > 1 { + var verbose int + if err := json.Unmarshal(r.Params[1], &verbose); err != nil { + return fmt.Errorf("second optional parameter 'verbose' must be an integer: %v", err) + } + optArgs = append(optArgs, verbose) + } + + newCmd, err := NewGetRawTransactionCmd(r.Id, txid, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetReceivedByAccountCmd is a type handling custom marshaling and +// unmarshaling of getreceivedbyaccount JSON RPC commands. +type GetReceivedByAccountCmd struct { + id interface{} + Account string + MinConf int +} + +// Enforce that GetReceivedByAccountCmd satisifies the Cmd interface. +var _ Cmd = &GetReceivedByAccountCmd{} + +// NewGetReceivedByAccountCmd creates a new GetReceivedByAccountCmd. +func NewGetReceivedByAccountCmd(id interface{}, account string, optArgs ...int) (*GetReceivedByAccountCmd, error) { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + var minconf = 1 + if len(optArgs) > 0 { + minconf = optArgs[0] + } + return &GetReceivedByAccountCmd{ + id: id, + Account: account, + MinConf: minconf, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetReceivedByAccountCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetReceivedByAccountCmd) Method() string { + return "getreceivedbyaccount" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetReceivedByAccountCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 2) + params[0] = cmd.Account + if cmd.MinConf != 1 { + params = append(params, cmd.MinConf) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetReceivedByAccountCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + var account string + if err := json.Unmarshal(r.Params[0], &account); err != nil { + return fmt.Errorf("first parameter 'account' must be a string: %v", err) + } + + optArgs := make([]int, 0, 1) + if len(r.Params) > 1 { + var minconf int + if err := json.Unmarshal(r.Params[1], &minconf); err != nil { + return fmt.Errorf("second optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + + newCmd, err := NewGetReceivedByAccountCmd(r.Id, account, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetReceivedByAddressCmd is a type handling custom marshaling and +// unmarshaling of getreceivedbyaddress JSON RPC commands. +type GetReceivedByAddressCmd struct { + id interface{} + Address string + MinConf int +} + +// Enforce that GetReceivedByAddressCmd satisifies the Cmd interface. +var _ Cmd = &GetReceivedByAddressCmd{} + +// NewGetReceivedByAddressCmd creates a new GetReceivedByAddressCmd. +func NewGetReceivedByAddressCmd(id interface{}, address string, optArgs ...int) (*GetReceivedByAddressCmd, error) { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + var minconf = 1 + if len(optArgs) > 0 { + minconf = optArgs[0] + } + return &GetReceivedByAddressCmd{ + id: id, + Address: address, + MinConf: minconf, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetReceivedByAddressCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetReceivedByAddressCmd) Method() string { + return "getreceivedbyaddress" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetReceivedByAddressCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 2) + params[0] = cmd.Address + if cmd.MinConf != 1 { + params = append(params, cmd.MinConf) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetReceivedByAddressCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + optArgs := make([]int, 0, 1) + if len(r.Params) > 1 { + var minconf int + if err := json.Unmarshal(r.Params[1], &minconf); err != nil { + return fmt.Errorf("second optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + + newCmd, err := NewGetReceivedByAddressCmd(r.Id, address, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetTransactionCmd is a type handling custom marshaling and +// unmarshaling of gettransaction JSON RPC commands. +type GetTransactionCmd struct { + id interface{} + Txid string +} + +// Enforce that GetTransactionCmd satisifies the Cmd interface. +var _ Cmd = &GetTransactionCmd{} + +// NewGetTransactionCmd creates a new GetTransactionCmd. +func NewGetTransactionCmd(id interface{}, txid string) (*GetTransactionCmd, error) { + return &GetTransactionCmd{ + id: id, + Txid: txid, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetTransactionCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetTransactionCmd) Method() string { + return "gettransaction" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetTransactionCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Txid, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetTransactionCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var txid string + if err := json.Unmarshal(r.Params[0], &txid); err != nil { + return fmt.Errorf("first parameter 'txid' must be a string: %v", err) + } + + newCmd, err := NewGetTransactionCmd(r.Id, txid) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetTxOutCmd is a type handling custom marshaling and +// unmarshaling of gettxout JSON RPC commands. +type GetTxOutCmd struct { + id interface{} + Txid string + Output int + IncludeMempool bool +} + +// Enforce that GetTxOutCmd satisifies the Cmd interface. +var _ Cmd = &GetTxOutCmd{} + +// NewGetTxOutCmd creates a new GetTxOutCmd. +func NewGetTxOutCmd(id interface{}, txid string, output int, optArgs ...bool) (*GetTxOutCmd, error) { + mempool := true + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + mempool = optArgs[0] + } + return &GetTxOutCmd{ + id: id, + Txid: txid, + Output: output, + IncludeMempool: mempool, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetTxOutCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetTxOutCmd) Method() string { + return "gettxout" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetTxOutCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 2, 3) + params[0] = cmd.Txid + params[1] = cmd.Output + if !cmd.IncludeMempool { + params = append(params, cmd.IncludeMempool) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetTxOutCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 3 || len(r.Params) < 2 { + return ErrWrongNumberOfParams + } + + var txid string + if err := json.Unmarshal(r.Params[0], &txid); err != nil { + return fmt.Errorf("first parameter 'txid' must be a string: %v", err) + } + + var output int + if err := json.Unmarshal(r.Params[1], &output); err != nil { + return fmt.Errorf("second parameter 'output' must be an integer: %v", err) + } + + optArgs := make([]bool, 0, 1) + if len(r.Params) > 2 { + var mempool bool + if err := json.Unmarshal(r.Params[2], &mempool); err != nil { + return fmt.Errorf("third optional parameter 'includemempool' must be a bool: %v", err) + } + optArgs = append(optArgs, mempool) + } + + newCmd, err := NewGetTxOutCmd(r.Id, txid, int(output), optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetTxOutSetInfoCmd is a type handling custom marshaling and +// unmarshaling of gettxoutsetinfo JSON RPC commands. +type GetTxOutSetInfoCmd struct { + id interface{} +} + +// Enforce that GetTxOutSetInfoCmd satisifies the Cmd interface. +var _ Cmd = &GetTxOutSetInfoCmd{} + +// NewGetTxOutSetInfoCmd creates a new GetTxOutSetInfoCmd. +func NewGetTxOutSetInfoCmd(id interface{}) (*GetTxOutSetInfoCmd, error) { + return &GetTxOutSetInfoCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetTxOutSetInfoCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetTxOutSetInfoCmd) Method() string { + return "gettxoutsetinfo" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetTxOutSetInfoCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetTxOutSetInfoCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewGetTxOutSetInfoCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// GetWorkCmd is a type handling custom marshaling and +// unmarshaling of getwork JSON RPC commands. +type GetWorkCmd struct { + id interface{} + Data string `json:"data,omitempty"` +} + +// Enforce that GetWorkCmd satisifies the Cmd interface. +var _ Cmd = &GetWorkCmd{} + +// NewGetWorkCmd creates a new GetWorkCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewGetWorkCmd(id interface{}, optArgs ...string) (*GetWorkCmd, error) { + var data string + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + data = optArgs[0] + } + return &GetWorkCmd{ + id: id, + Data: data, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *GetWorkCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *GetWorkCmd) Method() string { + return "getwork" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *GetWorkCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 1) + if cmd.Data != "" { + params = append(params, cmd.Data) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *GetWorkCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + if len(r.Params) > 1 { + return ErrWrongNumberOfParams + } + + var data string + if len(r.Params) > 0 { + if err := json.Unmarshal(r.Params[0], &data); err != nil { + return fmt.Errorf("first optional parameter 'data' must be a string: %v", err) + } + } + + newCmd, err := NewGetWorkCmd(r.Id, data) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// HelpCmd is a type handling custom marshaling and +// unmarshaling of help JSON RPC commands. +type HelpCmd struct { + id interface{} + Command string +} + +// Enforce that HelpCmd satisifies the Cmd interface. +var _ Cmd = &HelpCmd{} + +// NewHelpCmd creates a new HelpCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewHelpCmd(id interface{}, optArgs ...string) (*HelpCmd, error) { + + var command string + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + command = optArgs[0] + } + return &HelpCmd{ + id: id, + Command: command, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *HelpCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *HelpCmd) Method() string { + return "help" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *HelpCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 1) + if cmd.Command != "" { + params = append(params, cmd.Command) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *HelpCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 1 { + return ErrWrongNumberOfParams + } + + optArgs := make([]string, 0, 1) + if len(r.Params) > 0 { + var command string + if err := json.Unmarshal(r.Params[0], &command); err != nil { + return fmt.Errorf("first optional parameter 'command' must be a string: %v", err) + } + optArgs = append(optArgs, command) + } + + newCmd, err := NewHelpCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ImportAddressCmd is a type handling custom marshaling and +// unmarshaling of importaddress JSON RPC commands. +type ImportAddressCmd struct { + id interface{} + Address string + Rescan bool +} + +// Enforce that ImportAddressCmd satisifies the Cmd interface. +var _ Cmd = &ImportAddressCmd{} + +// NewImportAddressCmd creates a new ImportAddressCmd. +func NewImportAddressCmd(id interface{}, address string, optArgs ...interface{}) (*ImportAddressCmd, error) { + rescan := true + var ok bool + + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + if len(optArgs) > 0 { + rescan, ok = optArgs[0].(bool) + if !ok { + return nil, errors.New("first optional argument rescan is not a bool") + } + } + return &ImportAddressCmd{ + id: id, + Address: address, + Rescan: rescan, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ImportAddressCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ImportAddressCmd) Method() string { + return "importaddress" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ImportAddressCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 2) + params[0] = cmd.Address + if !cmd.Rescan { + params = append(params, cmd.Rescan) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ImportAddressCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) == 0 || len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + var optArgs []interface{} + if len(r.Params) > 1 { + var rescan bool + if err := json.Unmarshal(r.Params[1], &rescan); err != nil { + return fmt.Errorf("first optional parameter 'rescan' must be a bool: %v", err) + } + optArgs = append(optArgs, rescan) + } + + newCmd, err := NewImportAddressCmd(r.Id, address, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ImportPubKeyCmd is a type handling custom marshaling and +// unmarshaling of importpubkey JSON RPC commands. +type ImportPubKeyCmd struct { + id interface{} + PubKey string + Rescan bool +} + +// Enforce that ImportPubKeyCmd satisifies the Cmd interface. +var _ Cmd = &ImportPubKeyCmd{} + +// NewImportPubKeyCmd creates a new ImportPubKeyCmd. +func NewImportPubKeyCmd(id interface{}, pubkey string, optArgs ...interface{}) (*ImportPubKeyCmd, error) { + rescan := true + var ok bool + + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + if len(optArgs) > 0 { + rescan, ok = optArgs[0].(bool) + if !ok { + return nil, errors.New("first optional argument rescan is not a bool") + } + } + return &ImportPubKeyCmd{ + id: id, + PubKey: pubkey, + Rescan: rescan, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ImportPubKeyCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ImportPubKeyCmd) Method() string { + return "importpubkey" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ImportPubKeyCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 2) + params[0] = cmd.PubKey + if !cmd.Rescan { + params = append(params, cmd.Rescan) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ImportPubKeyCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) == 0 || len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + var pubkey string + if err := json.Unmarshal(r.Params[0], &pubkey); err != nil { + return fmt.Errorf("first parameter 'pubkey' must be a string: %v", err) + } + + var optArgs []interface{} + if len(r.Params) > 1 { + var rescan bool + if err := json.Unmarshal(r.Params[1], &rescan); err != nil { + return fmt.Errorf("first optional parameter 'rescan' must be a bool: %v", err) + } + optArgs = append(optArgs, rescan) + } + + newCmd, err := NewImportPubKeyCmd(r.Id, pubkey, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ImportPrivKeyCmd is a type handling custom marshaling and +// unmarshaling of importprivkey JSON RPC commands. +type ImportPrivKeyCmd struct { + id interface{} + PrivKey string + Label string + Rescan bool +} + +// Enforce that ImportPrivKeyCmd satisifies the Cmd interface. +var _ Cmd = &ImportPrivKeyCmd{} + +// NewImportPrivKeyCmd creates a new ImportPrivKeyCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewImportPrivKeyCmd(id interface{}, privkey string, optArgs ...interface{}) (*ImportPrivKeyCmd, error) { + var label string + rescan := true + var ok bool + + if len(optArgs) > 2 { + return nil, ErrTooManyOptArgs + } + if len(optArgs) > 0 { + label, ok = optArgs[0].(string) + if !ok { + return nil, errors.New("first optional argument label is not a string") + } + } + if len(optArgs) > 1 { + rescan, ok = optArgs[1].(bool) + if !ok { + return nil, errors.New("first optional argument rescan is not a bool") + } + } + return &ImportPrivKeyCmd{ + id: id, + PrivKey: privkey, + Label: label, + Rescan: rescan, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ImportPrivKeyCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ImportPrivKeyCmd) Method() string { + return "importprivkey" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ImportPrivKeyCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 3) + params[0] = cmd.PrivKey + if cmd.Label != "" || !cmd.Rescan { + params = append(params, cmd.Label) + } + if !cmd.Rescan { + params = append(params, cmd.Rescan) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ImportPrivKeyCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) == 0 || len(r.Params) > 3 { + return ErrWrongNumberOfParams + } + + var privkey string + if err := json.Unmarshal(r.Params[0], &privkey); err != nil { + return fmt.Errorf("first parameter 'privkey' must be a string: %v", err) + } + + optArgs := make([]interface{}, 0, 2) + if len(r.Params) > 1 { + var label string + if err := json.Unmarshal(r.Params[1], &label); err != nil { + return fmt.Errorf("second optional parameter 'label' must be a string: %v", err) + } + optArgs = append(optArgs, label) + } + + if len(r.Params) > 2 { + var rescan bool + if err := json.Unmarshal(r.Params[2], &rescan); err != nil { + return fmt.Errorf("third optional parameter 'rescan' must be a bool: %v", err) + } + optArgs = append(optArgs, rescan) + } + + newCmd, err := NewImportPrivKeyCmd(r.Id, privkey, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ImportWalletCmd is a type handling custom marshaling and +// unmarshaling of importwallet JSON RPC commands. +type ImportWalletCmd struct { + id interface{} + Filename string +} + +// Enforce that ImportWalletCmd satisifies the Cmd interface. +var _ Cmd = &ImportWalletCmd{} + +// NewImportWalletCmd creates a new ImportWalletCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewImportWalletCmd(id interface{}, filename string) (*ImportWalletCmd, error) { + return &ImportWalletCmd{ + id: id, + Filename: filename, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ImportWalletCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ImportWalletCmd) Method() string { + return "importwallet" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ImportWalletCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Filename, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ImportWalletCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var filename string + if err := json.Unmarshal(r.Params[0], &filename); err != nil { + return fmt.Errorf("first parameter 'filename' must be a string: %v", err) + } + + newCmd, err := NewImportWalletCmd(r.Id, filename) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// InvalidateBlockCmd is a type handling custom marshaling and +// unmarshaling of invalidateblock JSON RPC commands. +type InvalidateBlockCmd struct { + id interface{} + BlockHash string +} + +// Enforce that InvalidateBlockCmd satisifies the Cmd interface. +var _ Cmd = &InvalidateBlockCmd{} + +// NewInvalidateBlockCmd creates a new InvalidateBlockCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewInvalidateBlockCmd(id interface{}, blockhash string) (*InvalidateBlockCmd, error) { + return &InvalidateBlockCmd{ + id: id, + BlockHash: blockhash, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *InvalidateBlockCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *InvalidateBlockCmd) Method() string { + return "invalidateblock" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *InvalidateBlockCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.BlockHash, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *InvalidateBlockCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var blockhash string + if err := json.Unmarshal(r.Params[0], &blockhash); err != nil { + return fmt.Errorf("first parameter 'hash' must be a string: %v", err) + } + + newCmd, err := NewInvalidateBlockCmd(r.Id, blockhash) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// KeyPoolRefillCmd is a type handling custom marshaling and +// unmarshaling of keypoolrefill JSON RPC commands. +type KeyPoolRefillCmd struct { + id interface{} + NewSize uint +} + +// Enforce that KeyPoolRefillCmd satisifies the Cmd interface. +var _ Cmd = &KeyPoolRefillCmd{} + +// NewKeyPoolRefillCmd creates a new KeyPoolRefillCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewKeyPoolRefillCmd(id interface{}, optArgs ...uint) (*KeyPoolRefillCmd, error) { + newSize := uint(0) + + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + newSize = optArgs[0] + } + + return &KeyPoolRefillCmd{ + id: id, + NewSize: newSize, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *KeyPoolRefillCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *KeyPoolRefillCmd) Method() string { + return "keypoolrefill" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *KeyPoolRefillCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 1) + if cmd.NewSize != 0 { + params = append(params, cmd.NewSize) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *KeyPoolRefillCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 1 { + return ErrWrongNumberOfParams + } + + optArgs := make([]uint, 0, 1) + if len(r.Params) > 0 { + var newsize uint + if err := json.Unmarshal(r.Params[0], &newsize); err != nil { + return fmt.Errorf("first optional parameter 'newsize' must be an unsigned integer: %v", err) + } + optArgs = append(optArgs, newsize) + } + + newCmd, err := NewKeyPoolRefillCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ListAccountsCmd is a type handling custom marshaling and +// unmarshaling of listaccounts JSON RPC commands. +type ListAccountsCmd struct { + id interface{} + MinConf int +} + +// Enforce that ListAccountsCmd satisifies the Cmd interface. +var _ Cmd = &ListAccountsCmd{} + +// NewListAccountsCmd creates a new ListAccountsCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewListAccountsCmd(id interface{}, optArgs ...int) (*ListAccountsCmd, error) { + var minconf = 1 + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + minconf = optArgs[0] + } + return &ListAccountsCmd{ + id: id, + MinConf: minconf, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ListAccountsCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ListAccountsCmd) Method() string { + return "listaccounts" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ListAccountsCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 1) + if cmd.MinConf != 1 { + params = append(params, cmd.MinConf) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ListAccountsCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 1 { + return ErrWrongNumberOfParams + } + + optArgs := make([]int, 0, 1) + if len(r.Params) == 1 { + var minconf int + if err := json.Unmarshal(r.Params[0], &minconf); err != nil { + return fmt.Errorf("first optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + + newCmd, err := NewListAccountsCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ListAddressGroupingsCmd is a type handling custom marshaling and +// unmarshaling of listaddressgroupings JSON RPC commands. +type ListAddressGroupingsCmd struct { + id interface{} +} + +// Enforce that ListAddressGroupingsCmd satisifies the Cmd interface. +var _ Cmd = &ListAddressGroupingsCmd{} + +// NewListAddressGroupingsCmd creates a new ListAddressGroupingsCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewListAddressGroupingsCmd(id interface{}) (*ListAddressGroupingsCmd, error) { + return &ListAddressGroupingsCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ListAddressGroupingsCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ListAddressGroupingsCmd) Method() string { + return "listaddressgroupings" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ListAddressGroupingsCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ListAddressGroupingsCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewListAddressGroupingsCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ListLockUnspentCmd is a type handling custom marshaling and +// unmarshaling of listlockunspent JSON RPC commands. +type ListLockUnspentCmd struct { + id interface{} +} + +// Enforce that ListLockUnspentCmd satisifies the Cmd interface. +var _ Cmd = &ListLockUnspentCmd{} + +// NewListLockUnspentCmd creates a new ListLockUnspentCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewListLockUnspentCmd(id interface{}) (*ListLockUnspentCmd, error) { + return &ListLockUnspentCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ListLockUnspentCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ListLockUnspentCmd) Method() string { + return "listlockunspent" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ListLockUnspentCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ListLockUnspentCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewListLockUnspentCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ListReceivedByAccountCmd is a type handling custom marshaling and +// unmarshaling of listreceivedbyaccount JSON RPC commands. +type ListReceivedByAccountCmd struct { + id interface{} + MinConf int + IncludeEmpty bool +} + +// Enforce that ListReceivedByAccountCmd satisifies the Cmd interface. +var _ Cmd = &ListReceivedByAccountCmd{} + +// NewListReceivedByAccountCmd creates a new ListReceivedByAccountCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewListReceivedByAccountCmd(id interface{}, optArgs ...interface{}) (*ListReceivedByAccountCmd, error) { + minconf := 1 + includeempty := false + + if len(optArgs) > 2 { + return nil, ErrWrongNumberOfParams + } + if len(optArgs) > 0 { + m, ok := optArgs[0].(int) + if !ok { + return nil, errors.New("first optional argument minconf is not an int") + } + minconf = m + } + if len(optArgs) > 1 { + ie, ok := optArgs[1].(bool) + if !ok { + return nil, errors.New("second optional argument includeempty is not a bool") + } + + includeempty = ie + } + return &ListReceivedByAccountCmd{ + id: id, + MinConf: minconf, + IncludeEmpty: includeempty, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ListReceivedByAccountCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ListReceivedByAccountCmd) Method() string { + return "listreceivedbyaccount" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ListReceivedByAccountCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 2) + if cmd.MinConf != 1 || cmd.IncludeEmpty != false { + params = append(params, cmd.MinConf) + } + if cmd.IncludeEmpty != false { + params = append(params, cmd.IncludeEmpty) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ListReceivedByAccountCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + optArgs := make([]interface{}, 0, 2) + if len(r.Params) > 0 { + var minconf int + if err := json.Unmarshal(r.Params[0], &minconf); err != nil { + return fmt.Errorf("first optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + if len(r.Params) > 1 { + var includeempty bool + if err := json.Unmarshal(r.Params[1], &includeempty); err != nil { + return fmt.Errorf("second optional parameter 'includeempty' must be a bool: %v", err) + } + optArgs = append(optArgs, includeempty) + } + + newCmd, err := NewListReceivedByAccountCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ListReceivedByAddressCmd is a type handling custom marshaling and +// unmarshaling of listreceivedbyaddress JSON RPC commands. +type ListReceivedByAddressCmd struct { + id interface{} + MinConf int + IncludeEmpty bool +} + +// Enforce that ListReceivedByAddressCmd satisifies the Cmd interface. +var _ Cmd = &ListReceivedByAddressCmd{} + +// NewListReceivedByAddressCmd creates a new ListReceivedByAddressCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewListReceivedByAddressCmd(id interface{}, optArgs ...interface{}) (*ListReceivedByAddressCmd, error) { + minconf := 1 + includeempty := false + + if len(optArgs) > 2 { + return nil, ErrWrongNumberOfParams + } + if len(optArgs) > 0 { + m, ok := optArgs[0].(int) + if !ok { + return nil, errors.New("first optional argument minconf is not an int") + } + minconf = m + } + if len(optArgs) > 1 { + ie, ok := optArgs[1].(bool) + if !ok { + return nil, errors.New("second optional argument includeempty is not a bool") + } + + includeempty = ie + } + return &ListReceivedByAddressCmd{ + id: id, + MinConf: minconf, + IncludeEmpty: includeempty, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ListReceivedByAddressCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ListReceivedByAddressCmd) Method() string { + return "listreceivedbyaddress" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ListReceivedByAddressCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 2) + if cmd.MinConf != 1 || cmd.IncludeEmpty != false { + params = append(params, cmd.MinConf) + } + if cmd.IncludeEmpty != false { + params = append(params, cmd.IncludeEmpty) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ListReceivedByAddressCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + optArgs := make([]interface{}, 0, 2) + if len(r.Params) > 0 { + var minconf int + if err := json.Unmarshal(r.Params[0], &minconf); err != nil { + return fmt.Errorf("first optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + if len(r.Params) > 1 { + var includeempty bool + if err := json.Unmarshal(r.Params[1], &includeempty); err != nil { + return fmt.Errorf("second optional parameter 'includeempty' must be a bool: %v", err) + } + optArgs = append(optArgs, includeempty) + } + + newCmd, err := NewListReceivedByAddressCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ListSinceBlockCmd is a type handling custom marshaling and +// unmarshaling of listsinceblock JSON RPC commands. +type ListSinceBlockCmd struct { + id interface{} + BlockHash string + TargetConfirmations int +} + +// Enforce that ListSinceBlockCmd satisifies the Cmd interface. +var _ Cmd = &ListSinceBlockCmd{} + +// NewListSinceBlockCmd creates a new ListSinceBlockCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewListSinceBlockCmd(id interface{}, optArgs ...interface{}) (*ListSinceBlockCmd, error) { + blockhash := "" + targetconfirmations := 1 + + if len(optArgs) > 2 { + return nil, ErrWrongNumberOfParams + } + if len(optArgs) > 0 { + bh, ok := optArgs[0].(string) + if !ok { + return nil, errors.New("first optional argument blockhash is not a string") + } + blockhash = bh + } + if len(optArgs) > 1 { + tc, ok := optArgs[1].(int) + if !ok { + return nil, errors.New("second optional argument targetconfirmations is not an int") + } + + targetconfirmations = tc + } + return &ListSinceBlockCmd{ + id: id, + BlockHash: blockhash, + TargetConfirmations: targetconfirmations, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ListSinceBlockCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ListSinceBlockCmd) Method() string { + return "listsinceblock" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ListSinceBlockCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 2) + if cmd.BlockHash != "" || cmd.TargetConfirmations != 1 { + params = append(params, cmd.BlockHash) + } + if cmd.TargetConfirmations != 1 { + params = append(params, cmd.TargetConfirmations) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ListSinceBlockCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + optArgs := make([]interface{}, 0, 2) + if len(r.Params) > 0 { + var blockhash string + if err := json.Unmarshal(r.Params[0], &blockhash); err != nil { + return fmt.Errorf("first optional parameter 'blockhash' must be a string: %v", err) + } + optArgs = append(optArgs, blockhash) + } + if len(r.Params) > 1 { + var targetconfirmations int + if err := json.Unmarshal(r.Params[1], &targetconfirmations); err != nil { + return fmt.Errorf("second optional parameter 'targetconfirmations' must be an integer: %v", err) + } + optArgs = append(optArgs, targetconfirmations) + } + + newCmd, err := NewListSinceBlockCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ListTransactionsCmd is a type handling custom marshaling and +// unmarshaling of listtransactions JSON RPC commands. +type ListTransactionsCmd struct { + id interface{} + Account *string + Count int + From int +} + +// Enforce that ListTransactionsCmd satisifies the Cmd interface. +var _ Cmd = &ListTransactionsCmd{} + +// NewListTransactionsCmd creates a new ListTransactionsCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewListTransactionsCmd(id interface{}, optArgs ...interface{}) (*ListTransactionsCmd, error) { + var account *string + count := 10 + from := 0 + + if len(optArgs) > 3 { + return nil, ErrWrongNumberOfParams + } + if len(optArgs) > 0 { + ac, ok := optArgs[0].(string) + if !ok { + return nil, errors.New("first optional argument account is not a string") + } + account = &ac + } + if len(optArgs) > 1 { + cnt, ok := optArgs[1].(int) + if !ok { + return nil, errors.New("second optional argument count is not an int") + } + + count = cnt + } + if len(optArgs) > 2 { + frm, ok := optArgs[2].(int) + if !ok { + return nil, errors.New("third optional argument from is not an int") + } + + from = frm + } + return &ListTransactionsCmd{ + id: id, + Account: account, + Count: count, + From: from, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ListTransactionsCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ListTransactionsCmd) Method() string { + return "listtransactions" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ListTransactionsCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 3) + if cmd.Account != nil { + params = append(params, cmd.Account) + } + if cmd.Count != 10 || cmd.From != 0 { + params = append(params, cmd.Count) + } + if cmd.From != 0 { + params = append(params, cmd.From) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ListTransactionsCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 3 { + return ErrWrongNumberOfParams + } + + optArgs := make([]interface{}, 0, 3) + if len(r.Params) > 0 { + var account string + if err := json.Unmarshal(r.Params[0], &account); err != nil { + return fmt.Errorf("first optional parameter 'account' must be a string: %v", err) + } + optArgs = append(optArgs, account) + } + if len(r.Params) > 1 { + var count int + if err := json.Unmarshal(r.Params[1], &count); err != nil { + return fmt.Errorf("second optional parameter 'count' must be an integer: %v", err) + } + optArgs = append(optArgs, count) + } + if len(r.Params) > 2 { + var from int + if err := json.Unmarshal(r.Params[2], &from); err != nil { + return fmt.Errorf("third optional parameter 'from' must be an integer: %v", err) + } + optArgs = append(optArgs, from) + } + + newCmd, err := NewListTransactionsCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ListUnspentCmd is a type handling custom marshaling and +// unmarshaling of listunspent JSON RPC commands. +type ListUnspentCmd struct { + id interface{} + MinConf int + MaxConf int + Addresses []string +} + +// Enforce that ListUnspentCmd satisifies the Cmd interface. +var _ Cmd = &ListUnspentCmd{} + +// NewListUnspentCmd creates a new ListUnspentCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewListUnspentCmd(id interface{}, optArgs ...interface{}) (*ListUnspentCmd, error) { + minconf := 1 + maxconf := 999999 + var addresses []string + + if len(optArgs) > 3 { + return nil, ErrWrongNumberOfParams + } + if len(optArgs) > 0 { + m, ok := optArgs[0].(int) + if !ok { + return nil, errors.New("first optional argument minconf is not an int") + } + minconf = m + } + if len(optArgs) > 1 { + m, ok := optArgs[1].(int) + if !ok { + return nil, errors.New("second optional argument maxconf is not an int") + } + maxconf = m + } + if len(optArgs) > 2 { + a, ok := optArgs[2].([]string) + if !ok { + return nil, errors.New("third optional argument addresses is not an array of strings") + } + addresses = a + } + return &ListUnspentCmd{ + id: id, + MinConf: minconf, + MaxConf: maxconf, + Addresses: addresses, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ListUnspentCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ListUnspentCmd) Method() string { + return "listunspent" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ListUnspentCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 0, 3) + if cmd.MinConf != 1 || cmd.MaxConf != 99999 || len(cmd.Addresses) != 0 { + params = append(params, cmd.MinConf) + } + if cmd.MaxConf != 99999 || len(cmd.Addresses) != 0 { + params = append(params, cmd.MaxConf) + } + if len(cmd.Addresses) != 0 { + params = append(params, cmd.Addresses) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ListUnspentCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 3 { + return ErrWrongNumberOfParams + } + + optArgs := make([]interface{}, 0, 3) + if len(r.Params) > 0 { + var minconf int + if err := json.Unmarshal(r.Params[0], &minconf); err != nil { + return fmt.Errorf("first optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + if len(r.Params) > 1 { + var maxconf int + if err := json.Unmarshal(r.Params[1], &maxconf); err != nil { + return fmt.Errorf("second optional parameter 'maxconf' must be an integer: %v", err) + } + optArgs = append(optArgs, maxconf) + } + if len(r.Params) > 2 { + var addrs []string + if err := json.Unmarshal(r.Params[2], &addrs); err != nil { + return fmt.Errorf("third optional parameter 'addresses' must be an array of strings: %v", err) + } + optArgs = append(optArgs, addrs) + } + + newCmd, err := NewListUnspentCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// LockUnspentCmd is a type handling custom marshaling and +// unmarshaling of lockunspent JSON RPC commands. +type LockUnspentCmd struct { + id interface{} + Unlock bool + Transactions []TransactionInput +} + +// Enforce that LockUnspentCmd satisifies the Cmd interface. +var _ Cmd = &LockUnspentCmd{} + +// NewLockUnspentCmd creates a new LockUnspentCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewLockUnspentCmd(id interface{}, unlock bool, optArgs ...[]TransactionInput) (*LockUnspentCmd, error) { + var transactions []TransactionInput + + if len(optArgs) > 1 { + return nil, ErrWrongNumberOfParams + } + + if len(optArgs) > 0 { + transactions = optArgs[0] + } + + return &LockUnspentCmd{ + id: id, + Unlock: unlock, + Transactions: transactions, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *LockUnspentCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *LockUnspentCmd) Method() string { + return "lockunspent" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *LockUnspentCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 2) + params[0] = cmd.Unlock + if len(cmd.Transactions) > 0 { + params = append(params, cmd.Transactions) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *LockUnspentCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 || len(r.Params) < 1 { + return ErrWrongNumberOfParams + } + + var unlock bool + if err := json.Unmarshal(r.Params[0], &unlock); err != nil { + return fmt.Errorf("first parameter 'unlock' must be a bool: %v", err) + } + + optArgs := make([][]TransactionInput, 0, 1) + if len(r.Params) > 1 { + var transactions []TransactionInput + if err := json.Unmarshal(r.Params[1], &transactions); err != nil { + return fmt.Errorf("second optional parameter 'transactions' "+ + "must be a JSON array of transaction input JSON objects: %v", err) + } + optArgs = append(optArgs, transactions) + } + + newCmd, err := NewLockUnspentCmd(r.Id, unlock, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// MoveCmd is a type handling custom marshaling and +// unmarshaling of move JSON RPC commands. +type MoveCmd struct { + id interface{} + FromAccount string + ToAccount string + Amount int64 + MinConf int + Comment string +} + +// Enforce that MoveCmd satisifies the Cmd interface. +var _ Cmd = &MoveCmd{} + +// NewMoveCmd creates a new MoveCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewMoveCmd(id interface{}, fromaccount string, toaccount string, amount int64, optArgs ...interface{}) (*MoveCmd, error) { + minconf := 1 + comment := "" + + if len(optArgs) > 2 { + return nil, ErrWrongNumberOfParams + } + + if len(optArgs) > 0 { + m, ok := optArgs[0].(int) + if !ok { + return nil, errors.New("first optional parameter minconf is not a int64") + } + minconf = m + } + if len(optArgs) > 1 { + c, ok := optArgs[1].(string) + if !ok { + return nil, errors.New("second optional parameter comment is not a string") + } + comment = c + } + + return &MoveCmd{ + id: id, + FromAccount: fromaccount, + ToAccount: toaccount, + Amount: amount, + MinConf: minconf, + Comment: comment, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *MoveCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *MoveCmd) Method() string { + return "move" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *MoveCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 3, 5) + params[0] = cmd.FromAccount + params[1] = cmd.ToAccount + params[2] = float64(cmd.Amount) / 1e8 //convert to BTC + if cmd.MinConf != 1 || cmd.Comment != "" { + params = append(params, cmd.MinConf) + } + if cmd.Comment != "" { + params = append(params, cmd.Comment) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *MoveCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 5 || len(r.Params) < 3 { + return ErrWrongNumberOfParams + } + + var fromaccount string + if err := json.Unmarshal(r.Params[0], &fromaccount); err != nil { + return fmt.Errorf("first parameter 'fromaccount' must be a string: %v", err) + } + + var toaccount string + if err := json.Unmarshal(r.Params[1], &toaccount); err != nil { + return fmt.Errorf("second parameter 'toaccount' must be a string: %v", err) + } + + var famount float64 + if err := json.Unmarshal(r.Params[2], &famount); err != nil { + return fmt.Errorf("third parameter 'amount' must be a number: %v", err) + } + amount, err := JSONToAmount(famount) + if err != nil { + return err + } + + optArgs := make([]interface{}, 0, 2) + if len(r.Params) > 3 { + var minconf int + if err := json.Unmarshal(r.Params[3], &minconf); err != nil { + return fmt.Errorf("fourth optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + if len(r.Params) > 4 { + var comment string + if err := json.Unmarshal(r.Params[4], &comment); err != nil { + return fmt.Errorf("fifth optional parameter 'comment' must be a string: %v", err) + } + optArgs = append(optArgs, comment) + } + + newCmd, err := NewMoveCmd(r.Id, fromaccount, toaccount, amount, + optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// PingCmd is a type handling custom marshaling and +// unmarshaling of ping JSON RPC commands. +type PingCmd struct { + id interface{} +} + +// Enforce that PingCmd satisifies the Cmd interface. +var _ Cmd = &PingCmd{} + +// NewPingCmd creates a new PingCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewPingCmd(id interface{}) (*PingCmd, error) { + return &PingCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *PingCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *PingCmd) Method() string { + return "ping" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *PingCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *PingCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewPingCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ReconsiderBlockCmd is a type handling custom marshaling and +// unmarshaling of reconsiderblock JSON RPC commands. +type ReconsiderBlockCmd struct { + id interface{} + BlockHash string +} + +// Enforce that ReconsiderBlockCmd satisifies the Cmd interface. +var _ Cmd = &ReconsiderBlockCmd{} + +// NewReconsiderBlockCmd creates a new ReconsiderBlockCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewReconsiderBlockCmd(id interface{}, blockhash string) (*ReconsiderBlockCmd, error) { + return &ReconsiderBlockCmd{ + id: id, + BlockHash: blockhash, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ReconsiderBlockCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ReconsiderBlockCmd) Method() string { + return "reconsiderblock" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ReconsiderBlockCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.BlockHash, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ReconsiderBlockCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var blockhash string + if err := json.Unmarshal(r.Params[0], &blockhash); err != nil { + return fmt.Errorf("first parameter 'hash' must be a string: %v", err) + } + + newCmd, err := NewReconsiderBlockCmd(r.Id, blockhash) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// SearchRawTransactionsCmd is a type handling custom marshaling and +// unmarshaling of sendrawtransactions JSON RPC commands. +type SearchRawTransactionsCmd struct { + id interface{} + Address string + Verbose int + Skip int + Count int +} + +// NewSearchRawTransactionsCmd creates a new SearchRawTransactionsCmd. +func NewSearchRawTransactionsCmd(id interface{}, address string, + optArgs ...interface{}) (*SearchRawTransactionsCmd, error) { + var skip int + verbose := 1 + count := 100 + + if len(optArgs) > 3 { + return nil, ErrTooManyOptArgs + } + + if len(optArgs) > 0 { + v, ok := optArgs[0].(int) + if !ok { + return nil, errors.New("first optional argument verbose is not a int") + } + + verbose = v + } + if len(optArgs) > 1 { + s, ok := optArgs[1].(int) + if !ok { + return nil, errors.New("second optional argument skip is not an int") + } + skip = s + } + if len(optArgs) > 2 { + c, ok := optArgs[2].(int) + if !ok { + return nil, errors.New("third optional argument count is not an int") + } + + count = c + } + + return &SearchRawTransactionsCmd{ + id: id, + Address: address, + Verbose: verbose, + Skip: skip, + Count: count, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SearchRawTransactionsCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SearchRawTransactionsCmd) Method() string { + return "searchrawtransactions" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SearchRawTransactionsCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 4) + params[0] = cmd.Address + if cmd.Verbose != 1 || cmd.Skip != 0 || cmd.Count != 100 { + params = append(params, cmd.Verbose) + } + if cmd.Skip != 0 || cmd.Count != 100 { + params = append(params, cmd.Skip) + } + if cmd.Count != 100 { + params = append(params, cmd.Count) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SearchRawTransactionsCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) == 0 || len(r.Params) > 4 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + optArgs := make([]interface{}, 0, 3) + if len(r.Params) > 1 { + var verbose int + if err := json.Unmarshal(r.Params[1], &verbose); err != nil { + return fmt.Errorf("second optional parameter 'verbose' must be an int: %v", err) + } + optArgs = append(optArgs, verbose) + } + if len(r.Params) > 2 { + var skip int + if err := json.Unmarshal(r.Params[2], &skip); err != nil { + return fmt.Errorf("third optional parameter 'skip' must be an int: %v", err) + } + optArgs = append(optArgs, skip) + } + if len(r.Params) > 3 { + var count int + if err := json.Unmarshal(r.Params[3], &count); err != nil { + return fmt.Errorf("fourth optional parameter 'count' must be an int: %v", err) + } + optArgs = append(optArgs, count) + } + + newCmd, err := NewSearchRawTransactionsCmd(r.Id, address, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// Enforce that SearchRawTransactionsCmd satisifies the Cmd interface. +var _ Cmd = &SearchRawTransactionsCmd{} + +// SendFromCmd is a type handling custom marshaling and +// unmarshaling of sendfrom JSON RPC commands. +type SendFromCmd struct { + id interface{} + FromAccount string + ToAddress string + Amount int64 + MinConf int + Comment string + CommentTo string +} + +// Enforce that SendFromCmd satisifies the Cmd interface. +var _ Cmd = &SendFromCmd{} + +// NewSendFromCmd creates a new SendFromCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSendFromCmd(id interface{}, fromaccount string, toaddress string, amount int64, optArgs ...interface{}) (*SendFromCmd, error) { + minconf := 1 + comment := "" + commentto := "" + + if len(optArgs) > 3 { + return nil, ErrWrongNumberOfParams + } + + if len(optArgs) > 0 { + m, ok := optArgs[0].(int) + if !ok { + return nil, errors.New("first optional parameter minconf is not a int64") + } + minconf = m + } + if len(optArgs) > 1 { + c, ok := optArgs[1].(string) + if !ok { + return nil, errors.New("second optional parameter comment is not a string") + } + comment = c + } + + if len(optArgs) > 2 { + cto, ok := optArgs[2].(string) + if !ok { + return nil, errors.New("third optional parameter commentto is not a string") + } + commentto = cto + } + + return &SendFromCmd{ + id: id, + FromAccount: fromaccount, + ToAddress: toaddress, + Amount: amount, + MinConf: minconf, + Comment: comment, + CommentTo: commentto, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SendFromCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SendFromCmd) Method() string { + return "sendfrom" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SendFromCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 3, 6) + params[0] = cmd.FromAccount + params[1] = cmd.ToAddress + params[2] = float64(cmd.Amount) / 1e8 //convert to BTC + if cmd.MinConf != 1 || cmd.Comment != "" || cmd.CommentTo != "" { + params = append(params, cmd.MinConf) + } + if cmd.Comment != "" || cmd.CommentTo != "" { + params = append(params, cmd.Comment) + } + if cmd.CommentTo != "" { + params = append(params, cmd.CommentTo) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SendFromCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 6 || len(r.Params) < 3 { + return ErrWrongNumberOfParams + } + + var fromaccount string + if err := json.Unmarshal(r.Params[0], &fromaccount); err != nil { + return fmt.Errorf("first parameter 'fromaccount' must be a string: %v", err) + } + + var toaddress string + if err := json.Unmarshal(r.Params[1], &toaddress); err != nil { + return fmt.Errorf("second parameter 'toaddress' must be a string: %v", err) + } + + var famount float64 + if err := json.Unmarshal(r.Params[2], &famount); err != nil { + return fmt.Errorf("third parameter 'amount' must be a number: %v", err) + } + amount, err := JSONToAmount(famount) + if err != nil { + return err + } + + optArgs := make([]interface{}, 0, 3) + if len(r.Params) > 3 { + var minconf int + if err := json.Unmarshal(r.Params[3], &minconf); err != nil { + return fmt.Errorf("fourth optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + if len(r.Params) > 4 { + var comment string + if err := json.Unmarshal(r.Params[4], &comment); err != nil { + return fmt.Errorf("fifth optional parameter 'comment' must be a string: %v", err) + } + optArgs = append(optArgs, comment) + } + if len(r.Params) > 5 { + var commentto string + if err := json.Unmarshal(r.Params[5], &commentto); err != nil { + return fmt.Errorf("sixth optional parameter 'commentto' must be a string: %v", err) + } + optArgs = append(optArgs, commentto) + } + + newCmd, err := NewSendFromCmd(r.Id, fromaccount, toaddress, amount, + optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// SendManyCmd is a type handling custom marshaling and +// unmarshaling of sendmany JSON RPC commands. +type SendManyCmd struct { + id interface{} + FromAccount string + Amounts map[string]int64 + MinConf int + Comment string +} + +// Enforce that SendManyCmd satisifies the Cmd interface. +var _ Cmd = &SendManyCmd{} + +// NewSendManyCmd creates a new SendManyCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSendManyCmd(id interface{}, fromaccount string, amounts map[string]int64, optArgs ...interface{}) (*SendManyCmd, error) { + minconf := 1 + comment := "" + + if len(optArgs) > 2 { + return nil, ErrWrongNumberOfParams + } + + if len(optArgs) > 0 { + m, ok := optArgs[0].(int) + if !ok { + return nil, errors.New("first optional parameter minconf is not a int64") + } + minconf = m + } + if len(optArgs) > 1 { + c, ok := optArgs[1].(string) + if !ok { + return nil, errors.New("second optional parameter comment is not a string") + } + comment = c + } + + return &SendManyCmd{ + id: id, + FromAccount: fromaccount, + Amounts: amounts, + MinConf: minconf, + Comment: comment, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SendManyCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SendManyCmd) Method() string { + return "sendmany" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SendManyCmd) MarshalJSON() ([]byte, error) { + floatAmounts := make(map[string]float64, len(cmd.Amounts)) + for k, v := range cmd.Amounts { + floatAmounts[k] = float64(v) / 1e8 + } + + params := make([]interface{}, 2, 4) + params[0] = cmd.FromAccount + params[1] = floatAmounts + if cmd.MinConf != 1 || cmd.Comment != "" { + params = append(params, cmd.MinConf) + } + if cmd.Comment != "" { + params = append(params, cmd.Comment) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SendManyCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 4 || len(r.Params) < 2 { + return ErrWrongNumberOfParams + } + + var fromaccount string + if err := json.Unmarshal(r.Params[0], &fromaccount); err != nil { + return fmt.Errorf("first parameter 'fromaccount' must be a string: %v", err) + } + + var famounts map[string]float64 + if err := json.Unmarshal(r.Params[1], &famounts); err != nil { + return fmt.Errorf("second parameter 'amounts' must be a JSON object of address to amount mappings: %v", err) + } + amounts := make(map[string]int64, len(famounts)) + for k, v := range famounts { + amount, err := JSONToAmount(v) + if err != nil { + return err + } + amounts[k] = amount + } + + optArgs := make([]interface{}, 0, 2) + if len(r.Params) > 2 { + var minconf int + if err := json.Unmarshal(r.Params[2], &minconf); err != nil { + return fmt.Errorf("third optional parameter 'minconf' must be an integer: %v", err) + } + optArgs = append(optArgs, minconf) + } + if len(r.Params) > 3 { + var comment string + if err := json.Unmarshal(r.Params[3], &comment); err != nil { + return fmt.Errorf("fourth optional parameter 'comment' must be a string: %v", err) + } + optArgs = append(optArgs, comment) + } + + newCmd, err := NewSendManyCmd(r.Id, fromaccount, amounts, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// SendRawTransactionCmd is a type handling custom marshaling and +// unmarshaling of sendrawtransaction JSON RPC commands. +type SendRawTransactionCmd struct { + id interface{} + HexTx string + AllowHighFees bool +} + +// Enforce that SendRawTransactionCmd satisifies the Cmd interface. +var _ Cmd = &SendRawTransactionCmd{} + +// NewSendRawTransactionCmd creates a new SendRawTransactionCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSendRawTransactionCmd(id interface{}, hextx string, optArgs ...bool) (*SendRawTransactionCmd, error) { + allowHighFees := false + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + if len(optArgs) == 1 { + allowHighFees = optArgs[0] + } + + return &SendRawTransactionCmd{ + id: id, + HexTx: hextx, + AllowHighFees: allowHighFees, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SendRawTransactionCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SendRawTransactionCmd) Method() string { + return "sendrawtransaction" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SendRawTransactionCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 2) + params[0] = cmd.HexTx + if cmd.AllowHighFees { + params = append(params, cmd.AllowHighFees) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SendRawTransactionCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 || len(r.Params) < 1 { + return ErrWrongNumberOfParams + } + + var hextx string + if err := json.Unmarshal(r.Params[0], &hextx); err != nil { + return fmt.Errorf("first parameter 'hextx' must be a string: %v", err) + } + + optArgs := make([]bool, 0, 1) + if len(r.Params) > 1 { + var allowHighFees bool + if err := json.Unmarshal(r.Params[1], &allowHighFees); err != nil { + return fmt.Errorf("second optional parameter 'allowhighfees' must be a bool: %v", err) + } + optArgs = append(optArgs, allowHighFees) + } + + newCmd, err := NewSendRawTransactionCmd(r.Id, hextx, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// SendToAddressCmd is a type handling custom marshaling and +// unmarshaling of sendtoaddress JSON RPC commands. +type SendToAddressCmd struct { + id interface{} + Address string + Amount int64 + Comment string + CommentTo string +} + +// Enforce that SendToAddressCmd satisifies the Cmd interface. +var _ Cmd = &SendToAddressCmd{} + +// NewSendToAddressCmd creates a new SendToAddressCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSendToAddressCmd(id interface{}, address string, amount int64, optArgs ...interface{}) (*SendToAddressCmd, error) { + comment := "" + commentto := "" + + if len(optArgs) > 2 { + return nil, ErrWrongNumberOfParams + } + + if len(optArgs) > 0 { + c, ok := optArgs[0].(string) + if !ok { + return nil, errors.New("first optional parameter comment is not a string") + } + comment = c + } + if len(optArgs) > 1 { + cto, ok := optArgs[1].(string) + if !ok { + return nil, errors.New("second optional parameter commentto is not a string") + } + commentto = cto + } + + return &SendToAddressCmd{ + id: id, + Address: address, + Amount: amount, + Comment: comment, + CommentTo: commentto, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SendToAddressCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SendToAddressCmd) Method() string { + return "sendtoaddress" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SendToAddressCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 2, 4) + params[0] = cmd.Address + params[1] = float64(cmd.Amount) / 1e8 //convert to BTC + if cmd.Comment != "" || cmd.CommentTo != "" { + params = append(params, cmd.Comment) + } + if cmd.CommentTo != "" { + params = append(params, cmd.CommentTo) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SendToAddressCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 4 || len(r.Params) < 2 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + var famount float64 + if err := json.Unmarshal(r.Params[1], &famount); err != nil { + return fmt.Errorf("second parameter 'amount' must be a number: %v", err) + } + amount, err := JSONToAmount(famount) + if err != nil { + return err + } + + optArgs := make([]interface{}, 0, 2) + if len(r.Params) > 2 { + var comment string + if err := json.Unmarshal(r.Params[2], &comment); err != nil { + return fmt.Errorf("third optional parameter 'comment' must be a string: %v", err) + } + optArgs = append(optArgs, comment) + } + if len(r.Params) > 3 { + var commentto string + if err := json.Unmarshal(r.Params[3], &commentto); err != nil { + return fmt.Errorf("fourth optional parameter 'commentto' must be a string: %v", err) + } + optArgs = append(optArgs, commentto) + } + + newCmd, err := NewSendToAddressCmd(r.Id, address, amount, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// SetAccountCmd is a type handling custom marshaling and +// unmarshaling of setaccount JSON RPC commands. +type SetAccountCmd struct { + id interface{} + Address string + Account string +} + +// Enforce that SetAccountCmd satisifies the Cmd interface. +var _ Cmd = &SetAccountCmd{} + +// NewSetAccountCmd creates a new SetAccountCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSetAccountCmd(id interface{}, address string, account string) (*SetAccountCmd, error) { + + return &SetAccountCmd{ + id: id, + Address: address, + Account: account, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SetAccountCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SetAccountCmd) Method() string { + return "setaccount" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SetAccountCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Address, + cmd.Account, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SetAccountCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 2 || len(r.Params) < 2 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + var account string + if err := json.Unmarshal(r.Params[1], &account); err != nil { + return fmt.Errorf("second parameter 'account' must be a string: %v", err) + } + + newCmd, err := NewSetAccountCmd(r.Id, address, account) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// SetGenerateCmd is a type handling custom marshaling and +// unmarshaling of setgenerate JSON RPC commands. +type SetGenerateCmd struct { + id interface{} + Generate bool + GenProcLimit int +} + +// Enforce that SetGenerateCmd satisifies the Cmd interface. +var _ Cmd = &SetGenerateCmd{} + +// NewSetGenerateCmd creates a new SetGenerateCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSetGenerateCmd(id interface{}, generate bool, optArgs ...int) (*SetGenerateCmd, error) { + genproclimit := -1 + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + if len(optArgs) == 1 { + genproclimit = optArgs[0] + } + + return &SetGenerateCmd{ + id: id, + Generate: generate, + GenProcLimit: genproclimit, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SetGenerateCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SetGenerateCmd) Method() string { + return "setgenerate" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SetGenerateCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 2) + params[0] = cmd.Generate + if cmd.GenProcLimit != -1 { + params = append(params, cmd.GenProcLimit) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SetGenerateCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 || len(r.Params) < 1 { + return ErrWrongNumberOfParams + } + + var generate bool + if err := json.Unmarshal(r.Params[0], &generate); err != nil { + return fmt.Errorf("first parameter 'generate' must be a bool: %v", err) + } + + optArgs := make([]int, 0, 1) + if len(r.Params) > 1 { + var genproclimit int + if err := json.Unmarshal(r.Params[1], &genproclimit); err != nil { + return fmt.Errorf("second optional parameter 'genproclimit' must be an integer: %v", err) + } + optArgs = append(optArgs, genproclimit) + } + + newCmd, err := NewSetGenerateCmd(r.Id, generate, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// SetTxFeeCmd is a type handling custom marshaling and +// unmarshaling of settxfee JSON RPC commands. +type SetTxFeeCmd struct { + id interface{} + Amount int64 +} + +// Enforce that SetTxFeeCmd satisifies the Cmd interface. +var _ Cmd = &SetTxFeeCmd{} + +// NewSetTxFeeCmd creates a new SetTxFeeCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSetTxFeeCmd(id interface{}, amount int64) (*SetTxFeeCmd, error) { + return &SetTxFeeCmd{ + id: id, + Amount: amount, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SetTxFeeCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SetTxFeeCmd) Method() string { + return "settxfee" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SetTxFeeCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + float64(cmd.Amount) / 1e8, //convert to BTC + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SetTxFeeCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var famount float64 + if err := json.Unmarshal(r.Params[0], &famount); err != nil { + return fmt.Errorf("first parameter 'amount' must be a number: %v", err) + } + amount, err := JSONToAmount(famount) + if err != nil { + return err + } + + newCmd, err := NewSetTxFeeCmd(r.Id, amount) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// SignMessageCmd is a type handling custom marshaling and +// unmarshaling of signmessage JSON RPC commands. +type SignMessageCmd struct { + id interface{} + Address string + Message string +} + +// Enforce that SignMessageCmd satisifies the Cmd interface. +var _ Cmd = &SignMessageCmd{} + +// NewSignMessageCmd creates a new SignMessageCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSignMessageCmd(id interface{}, address string, message string) (*SignMessageCmd, error) { + return &SignMessageCmd{ + id: id, + Address: address, + Message: message, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SignMessageCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SignMessageCmd) Method() string { + return "signmessage" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SignMessageCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Address, + cmd.Message, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SignMessageCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 2 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + var message string + if err := json.Unmarshal(r.Params[1], &message); err != nil { + return fmt.Errorf("second parameter 'message' must be a string: %v", err) + } + + newCmd, err := NewSignMessageCmd(r.Id, address, message) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// RawTxInput models the data needed for a raw tx input. +type RawTxInput struct { + Txid string `json:"txid"` + Vout uint32 `json:"vout"` + ScriptPubKey string `json:"scriptPubKey"` + RedeemScript string `json:"redeemScript"` +} + +// SignRawTransactionCmd is a type handling custom marshaling and +// unmarshaling of signrawtransaction JSON RPC commands. +type SignRawTransactionCmd struct { + id interface{} + RawTx string + Inputs []RawTxInput + PrivKeys []string + Flags string +} + +// Enforce that SignRawTransactionCmd satisifies the Cmd interface. +var _ Cmd = &SignRawTransactionCmd{} + +// NewSignRawTransactionCmd creates a new SignRawTransactionCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSignRawTransactionCmd(id interface{}, rawTx string, optArgs ...interface{}) (*SignRawTransactionCmd, error) { + var inputs []RawTxInput + var privkeys []string + var flags string + if len(optArgs) > 3 { + return nil, ErrTooManyOptArgs + } + if len(optArgs) > 0 { + ip, ok := optArgs[0].([]RawTxInput) + if !ok { + return nil, errors.New("first optional parameter inputs should be an array of RawTxInput") + } + + inputs = ip + } + if len(optArgs) > 1 { + pk, ok := optArgs[1].([]string) + if !ok { + return nil, errors.New("second optional parameter inputs should be an array of string") + } + + privkeys = pk + } + if len(optArgs) > 2 { + fl, ok := optArgs[2].(string) + if !ok { + return nil, errors.New("third optional parameter flags should be a string") + } + + flags = fl + } + return &SignRawTransactionCmd{ + id: id, + RawTx: rawTx, + Inputs: inputs, + PrivKeys: privkeys, + Flags: flags, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SignRawTransactionCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SignRawTransactionCmd) Method() string { + return "signrawtransaction" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SignRawTransactionCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 4) + params[0] = cmd.RawTx + if len(cmd.Inputs) > 0 || len(cmd.PrivKeys) > 0 || cmd.Flags != "" { + params = append(params, cmd.Inputs) + } + if len(cmd.PrivKeys) > 0 || cmd.Flags != "" { + params = append(params, cmd.PrivKeys) + } + if cmd.Flags != "" { + params = append(params, cmd.Flags) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SignRawTransactionCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 4 || len(r.Params) < 1 { + return ErrWrongNumberOfParams + } + + var rawtx string + if err := json.Unmarshal(r.Params[0], &rawtx); err != nil { + return fmt.Errorf("first parameter 'rawtx' must be a string: %v", err) + } + + optArgs := make([]interface{}, 0, 3) + if len(r.Params) > 1 { + var inputs []RawTxInput + if err := json.Unmarshal(r.Params[1], &inputs); err != nil { + return fmt.Errorf("second optional parameter 'inputs' "+ + "must be a JSON array of raw transaction input JSON objects: %v", err) + } + optArgs = append(optArgs, inputs) + } + + if len(r.Params) > 2 { + var privkeys []string + if err := json.Unmarshal(r.Params[2], &privkeys); err != nil { + return fmt.Errorf("third optional parameter 'privkeys' must be an array of strings: %v", err) + } + optArgs = append(optArgs, privkeys) + } + if len(r.Params) > 3 { + var flags string + if err := json.Unmarshal(r.Params[3], &flags); err != nil { + return fmt.Errorf("fourth optional parameter 'flags' must be a string: %v", err) + } + optArgs = append(optArgs, flags) + } + + newCmd, err := NewSignRawTransactionCmd(r.Id, rawtx, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// StopCmd is a type handling custom marshaling and +// unmarshaling of stop JSON RPC commands. +type StopCmd struct { + id interface{} +} + +// Enforce that StopCmd satisifies the Cmd interface. +var _ Cmd = &StopCmd{} + +// NewStopCmd creates a new StopCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewStopCmd(id interface{}) (*StopCmd, error) { + + return &StopCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *StopCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *StopCmd) Method() string { + return "stop" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *StopCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *StopCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewStopCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// 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 is a type handling custom marshaling and +// unmarshaling of submitblock JSON RPC commands. +type SubmitBlockCmd struct { + id interface{} + HexBlock string + Options *SubmitBlockOptions +} + +// Enforce that SubmitBlockCmd satisifies the Cmd interface. +var _ Cmd = &SubmitBlockCmd{} + +// NewSubmitBlockCmd creates a new SubmitBlockCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewSubmitBlockCmd(id interface{}, hexblock string, optArgs ...*SubmitBlockOptions) (*SubmitBlockCmd, error) { + var options *SubmitBlockOptions + if len(optArgs) > 0 { + if len(optArgs) > 1 { + return nil, ErrTooManyOptArgs + } + options = optArgs[0] + } + + return &SubmitBlockCmd{ + id: id, + HexBlock: hexblock, + Options: options, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *SubmitBlockCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *SubmitBlockCmd) Method() string { + return "submitblock" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *SubmitBlockCmd) MarshalJSON() ([]byte, error) { + params := make([]interface{}, 1, 2) + params[0] = cmd.HexBlock + if cmd.Options != nil { + params = append(params, cmd.Options) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *SubmitBlockCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 || len(r.Params) < 1 { + return ErrWrongNumberOfParams + } + + var hexblock string + if err := json.Unmarshal(r.Params[0], &hexblock); err != nil { + return fmt.Errorf("first parameter 'hexblock' must be a string: %v", err) + } + + optArgs := make([]*SubmitBlockOptions, 0, 1) + if len(r.Params) == 2 { + var options SubmitBlockOptions + if err := json.Unmarshal(r.Params[1], &options); err != nil { + return fmt.Errorf("second optional parameter 'options' must "+ + "be a JSON object of submit block options: %v", err) + } + optArgs = append(optArgs, &options) + } + + newCmd, err := NewSubmitBlockCmd(r.Id, hexblock, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// ValidateAddressCmd is a type handling custom marshaling and +// unmarshaling of validateaddress JSON RPC commands. +type ValidateAddressCmd struct { + id interface{} + Address string +} + +// Enforce that ValidateAddressCmd satisifies the Cmd interface. +var _ Cmd = &ValidateAddressCmd{} + +// NewValidateAddressCmd creates a new ValidateAddressCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewValidateAddressCmd(id interface{}, address string) (*ValidateAddressCmd, error) { + + return &ValidateAddressCmd{ + id: id, + Address: address, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *ValidateAddressCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *ValidateAddressCmd) Method() string { + return "validateaddress" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *ValidateAddressCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Address, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *ValidateAddressCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 1 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + newCmd, err := NewValidateAddressCmd(r.Id, address) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// VerifyChainCmd is a type handling custom marshaling and +// unmarshaling of verifychain JSON RPC commands. +type VerifyChainCmd struct { + id interface{} + CheckLevel int32 + CheckDepth int32 +} + +// Enforce that VerifyChainCmd satisifies the Cmd interface. +var _ Cmd = &VerifyChainCmd{} + +// NewVerifyChainCmd creates a new VerifyChainCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewVerifyChainCmd(id interface{}, optArgs ...int32) (*VerifyChainCmd, error) { + // bitcoind default, but they do vary it based on cli args. + var checklevel int32 = 3 + var checkdepth int32 = 288 + + if len(optArgs) > 0 { + if len(optArgs) > 2 { + return nil, ErrTooManyOptArgs + } + checklevel = optArgs[0] + + if len(optArgs) > 1 { + checkdepth = optArgs[1] + } + } + + return &VerifyChainCmd{ + id: id, + CheckLevel: checklevel, + CheckDepth: checkdepth, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *VerifyChainCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *VerifyChainCmd) Method() string { + return "verifychain" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *VerifyChainCmd) MarshalJSON() ([]byte, error) { + // XXX(oga) magic numbers + params := make([]interface{}, 0, 2) + if cmd.CheckLevel != 3 || cmd.CheckDepth != 288 { + params = append(params, cmd.CheckLevel) + } + if cmd.CheckDepth != 288 { + params = append(params, cmd.CheckDepth) + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *VerifyChainCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) > 2 { + return ErrWrongNumberOfParams + } + + optArgs := make([]int32, 0, 2) + if len(r.Params) > 0 { + var checklevel int32 + if err := json.Unmarshal(r.Params[0], &checklevel); err != nil { + return fmt.Errorf("first optional parameter 'checklevel' must be a 32-bit integer: %v", err) + } + optArgs = append(optArgs, checklevel) + } + + if len(r.Params) > 1 { + var checkdepth int32 + if err := json.Unmarshal(r.Params[1], &checkdepth); err != nil { + return fmt.Errorf("second optional parameter 'checkdepth' must be a 32-bit integer: %v", err) + } + optArgs = append(optArgs, checkdepth) + } + + newCmd, err := NewVerifyChainCmd(r.Id, optArgs...) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// VerifyMessageCmd is a type handling custom marshaling and +// unmarshaling of verifymessage JSON RPC commands. +type VerifyMessageCmd struct { + id interface{} + Address string + Signature string + Message string +} + +// Enforce that VerifyMessageCmd satisifies the Cmd interface. +var _ Cmd = &VerifyMessageCmd{} + +// NewVerifyMessageCmd creates a new VerifyMessageCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewVerifyMessageCmd(id interface{}, address string, signature string, + message string) (*VerifyMessageCmd, error) { + + return &VerifyMessageCmd{ + id: id, + Address: address, + Signature: signature, + Message: message, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *VerifyMessageCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *VerifyMessageCmd) Method() string { + return "verifymessage" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *VerifyMessageCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Address, + cmd.Signature, + cmd.Message, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *VerifyMessageCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 3 { + return ErrWrongNumberOfParams + } + + var address string + if err := json.Unmarshal(r.Params[0], &address); err != nil { + return fmt.Errorf("first parameter 'address' must be a string: %v", err) + } + + var signature string + if err := json.Unmarshal(r.Params[1], &signature); err != nil { + return fmt.Errorf("second parameter 'signature' must be a string: %v", err) + } + + var message string + if err := json.Unmarshal(r.Params[2], &message); err != nil { + return fmt.Errorf("third parameter 'message' must be a string: %v", err) + } + + newCmd, err := NewVerifyMessageCmd(r.Id, address, signature, message) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// WalletLockCmd is a type handling custom marshaling and +// unmarshaling of walletlock JSON RPC commands. +type WalletLockCmd struct { + id interface{} +} + +// Enforce that WalletLockCmd satisifies the Cmd interface. +var _ Cmd = &WalletLockCmd{} + +// NewWalletLockCmd creates a new WalletLockCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewWalletLockCmd(id interface{}) (*WalletLockCmd, error) { + + return &WalletLockCmd{ + id: id, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *WalletLockCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *WalletLockCmd) Method() string { + return "walletlock" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *WalletLockCmd) MarshalJSON() ([]byte, error) { + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), []interface{}{}) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *WalletLockCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 0 { + return ErrWrongNumberOfParams + } + + newCmd, err := NewWalletLockCmd(r.Id) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// WalletPassphraseCmd is a type handling custom marshaling and +// unmarshaling of walletpassphrase JSON RPC commands. +type WalletPassphraseCmd struct { + id interface{} + Passphrase string + Timeout int64 +} + +// Enforce that WalletPassphraseCmd satisifies the Cmd interface. +var _ Cmd = &WalletPassphraseCmd{} + +// NewWalletPassphraseCmd creates a new WalletPassphraseCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewWalletPassphraseCmd(id interface{}, passphrase string, timeout int64) (*WalletPassphraseCmd, error) { + + return &WalletPassphraseCmd{ + id: id, + Passphrase: passphrase, + Timeout: timeout, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *WalletPassphraseCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *WalletPassphraseCmd) Method() string { + return "walletpassphrase" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *WalletPassphraseCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.Passphrase, + cmd.Timeout, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *WalletPassphraseCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 2 { + return ErrWrongNumberOfParams + } + + var passphrase string + if err := json.Unmarshal(r.Params[0], &passphrase); err != nil { + return fmt.Errorf("first parameter 'passphrase' must be a string: %v", err) + } + + var timeout int64 + if err := json.Unmarshal(r.Params[1], &timeout); err != nil { + return fmt.Errorf("second parameter 'timeout' must be an integer: %v", err) + } + + newCmd, err := NewWalletPassphraseCmd(r.Id, passphrase, timeout) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} + +// WalletPassphraseChangeCmd is a type handling custom marshaling and +// unmarshaling of walletpassphrasechange JSON RPC commands. +type WalletPassphraseChangeCmd struct { + id interface{} + OldPassphrase string + NewPassphrase string +} + +// Enforce that WalletPassphraseChangeCmd satisifies the Cmd interface. +var _ Cmd = &WalletPassphraseChangeCmd{} + +// NewWalletPassphraseChangeCmd creates a new WalletPassphraseChangeCmd. Optionally a +// pointer to a TemplateRequest may be provided. +func NewWalletPassphraseChangeCmd(id interface{}, oldpassphrase, newpassphrase string) (*WalletPassphraseChangeCmd, error) { + + return &WalletPassphraseChangeCmd{ + id: id, + OldPassphrase: oldpassphrase, + NewPassphrase: newpassphrase, + }, nil +} + +// Id satisfies the Cmd interface by returning the id of the command. +func (cmd *WalletPassphraseChangeCmd) Id() interface{} { + return cmd.id +} + +// Method satisfies the Cmd interface by returning the json method. +func (cmd *WalletPassphraseChangeCmd) Method() string { + return "walletpassphrasechange" +} + +// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface. +func (cmd *WalletPassphraseChangeCmd) MarshalJSON() ([]byte, error) { + params := []interface{}{ + cmd.OldPassphrase, + cmd.NewPassphrase, + } + + // Fill and marshal a RawCmd. + raw, err := NewRawCmd(cmd.id, cmd.Method(), params) + if err != nil { + return nil, err + } + return json.Marshal(raw) +} + +// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of +// the Cmd interface. +func (cmd *WalletPassphraseChangeCmd) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd + var r RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + if len(r.Params) != 2 { + return ErrWrongNumberOfParams + } + + var oldpassphrase string + if err := json.Unmarshal(r.Params[0], &oldpassphrase); err != nil { + return fmt.Errorf("first parameter 'oldpassphrase' must be a string: %v", err) + } + + var newpassphrase string + if err := json.Unmarshal(r.Params[1], &newpassphrase); err != nil { + return fmt.Errorf("second parameter 'newpassphrase' must be a string: %v", err) + } + + newCmd, err := NewWalletPassphraseChangeCmd(r.Id, oldpassphrase, newpassphrase) + if err != nil { + return err + } + + *cmd = *newCmd + return nil +} diff --git a/btcjson/jsoncmd_test.go b/btcjson/jsoncmd_test.go new file mode 100644 index 00000000..c8e9fdca --- /dev/null +++ b/btcjson/jsoncmd_test.go @@ -0,0 +1,1814 @@ +// 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. + +// this has to be in the real json subpackage so we can mock up structs +package btcjson + +import ( + "encoding/json" + "reflect" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" +) + +var testID = float64(1) + +var defaultAccount = "" +var testAccount = "account" + +var jsoncmdtests = []struct { + name string + cmd string + f func() (Cmd, error) + result Cmd // after marshal and unmarshal +}{ + { + name: "basic", + cmd: "addmultisigaddress", + f: func() (Cmd, error) { + return NewAddMultisigAddressCmd(testID, 1, + []string{"foo", "bar"}) + }, + result: &AddMultisigAddressCmd{ + id: testID, + NRequired: 1, + Keys: []string{"foo", "bar"}, + Account: defaultAccount, + }, + }, + { + name: "+ optional", + cmd: "addmultisigaddress", + f: func() (Cmd, error) { + return NewAddMultisigAddressCmd(testID, 1, + []string{"foo", "bar"}, "address") + }, + result: &AddMultisigAddressCmd{ + id: testID, + NRequired: 1, + Keys: []string{"foo", "bar"}, + Account: "address", + }, + }, + // TODO(oga) Too many arguments to newaddmultisigaddress + { + name: "basic add", + cmd: "addnode", + f: func() (Cmd, error) { + return NewAddNodeCmd(testID, "address", + "add") + }, + result: &AddNodeCmd{ + id: testID, + Addr: "address", + SubCmd: "add", + }, + }, + { + name: "basic remove", + cmd: "addnode", + f: func() (Cmd, error) { + return NewAddNodeCmd(testID, "address", + "remove") + }, + result: &AddNodeCmd{ + id: testID, + Addr: "address", + SubCmd: "remove", + }, + }, + { + name: "basic onetry", + cmd: "addnode", + f: func() (Cmd, error) { + return NewAddNodeCmd(testID, "address", + "onetry") + }, + result: &AddNodeCmd{ + id: testID, + Addr: "address", + SubCmd: "onetry", + }, + }, + // TODO(oga) try invalid subcmds + { + name: "basic", + cmd: "backupwallet", + f: func() (Cmd, error) { + return NewBackupWalletCmd(testID, "destination") + }, + result: &BackupWalletCmd{ + id: testID, + Destination: "destination", + }, + }, + { + name: "basic", + cmd: "createmultisig", + f: func() (Cmd, error) { + return NewCreateMultisigCmd(testID, 1, + []string{"key1", "key2", "key3"}) + }, + result: &CreateMultisigCmd{ + id: testID, + NRequired: 1, + Keys: []string{"key1", "key2", "key3"}, + }, + }, + { + name: "basic", + cmd: "createrawtransaction", + f: func() (Cmd, error) { + return NewCreateRawTransactionCmd(testID, + []TransactionInput{ + {Txid: "tx1", Vout: 1}, + {Txid: "tx2", Vout: 3}}, + map[string]int64{"bob": 1, "bill": 2}) + }, + result: &CreateRawTransactionCmd{ + id: testID, + Inputs: []TransactionInput{ + {Txid: "tx1", Vout: 1}, + {Txid: "tx2", Vout: 3}, + }, + Amounts: map[string]int64{ + "bob": 1, + "bill": 2, + }, + }, + }, + { + name: "basic", + cmd: "debuglevel", + f: func() (Cmd, error) { + return NewDebugLevelCmd(testID, "debug") + }, + result: &DebugLevelCmd{ + id: testID, + LevelSpec: "debug", + }, + }, + { + name: "basic", + cmd: "decoderawtransaction", + f: func() (Cmd, error) { + return NewDecodeRawTransactionCmd(testID, + "thisisahexidecimaltransaction") + }, + result: &DecodeRawTransactionCmd{ + id: testID, + HexTx: "thisisahexidecimaltransaction", + }, + }, + { + name: "basic", + cmd: "decodescript", + f: func() (Cmd, error) { + return NewDecodeScriptCmd(testID, + "a bunch of hex") + }, + result: &DecodeScriptCmd{ + id: testID, + HexScript: "a bunch of hex", + }, + }, + { + name: "basic", + cmd: "dumpprivkey", + f: func() (Cmd, error) { + return NewDumpPrivKeyCmd(testID, + "address") + }, + result: &DumpPrivKeyCmd{ + id: testID, + Address: "address", + }, + }, + { + name: "basic", + cmd: "dumpwallet", + f: func() (Cmd, error) { + return NewDumpWalletCmd(testID, + "filename") + }, + result: &DumpWalletCmd{ + id: testID, + Filename: "filename", + }, + }, + { + name: "basic", + cmd: "encryptwallet", + f: func() (Cmd, error) { + return NewEncryptWalletCmd(testID, + "passphrase") + }, + result: &EncryptWalletCmd{ + id: testID, + Passphrase: "passphrase", + }, + }, + { + name: "basic", + cmd: "estimatefee", + f: func() (Cmd, error) { + return NewEstimateFeeCmd(testID, 1234) + }, + result: &EstimateFeeCmd{ + id: testID, + NumBlocks: 1234, + }, + }, + { + name: "basic", + cmd: "estimatepriority", + f: func() (Cmd, error) { + return NewEstimatePriorityCmd(testID, 1234) + }, + result: &EstimatePriorityCmd{ + id: testID, + NumBlocks: 1234, + }, + }, + { + name: "basic", + cmd: "getaccount", + f: func() (Cmd, error) { + return NewGetAccountCmd(testID, + "address") + }, + result: &GetAccountCmd{ + id: testID, + Address: "address", + }, + }, + { + name: "basic", + cmd: "getaccountaddress", + f: func() (Cmd, error) { + return NewGetAccountAddressCmd(testID, + testAccount) + }, + result: &GetAccountAddressCmd{ + id: testID, + Account: testAccount, + }, + }, + { + name: "basic true", + cmd: "getaddednodeinfo", + f: func() (Cmd, error) { + return NewGetAddedNodeInfoCmd(testID, true) + }, + result: &GetAddedNodeInfoCmd{ + id: testID, + Dns: true, + }, + }, + { + name: "basic false", + cmd: "getaddednodeinfo", + f: func() (Cmd, error) { + return NewGetAddedNodeInfoCmd(testID, false) + }, + result: &GetAddedNodeInfoCmd{ + id: testID, + Dns: false, + }, + }, + { + name: "basic withnode", + cmd: "getaddednodeinfo", + f: func() (Cmd, error) { + return NewGetAddedNodeInfoCmd(testID, true, + "thisisanode") + }, + result: &GetAddedNodeInfoCmd{ + id: testID, + Dns: true, + Node: "thisisanode", + }, + }, + { + name: "basic", + cmd: "getaddressesbyaccount", + f: func() (Cmd, error) { + return NewGetAddressesByAccountCmd(testID, + testAccount) + }, + result: &GetAddressesByAccountCmd{ + id: testID, + Account: testAccount, + }, + }, + { + name: "basic", + cmd: "getbalance", + f: func() (Cmd, error) { + return NewGetBalanceCmd(testID) + }, + result: &GetBalanceCmd{ + id: testID, + MinConf: 1, // the default + }, + }, + { + name: "basic + account", + cmd: "getbalance", + f: func() (Cmd, error) { + return NewGetBalanceCmd(testID, testAccount) + }, + result: &GetBalanceCmd{ + id: testID, + Account: &testAccount, + MinConf: 1, // the default + }, + }, + { + name: "basic + minconf", + cmd: "getbalance", + f: func() (Cmd, error) { + return NewGetBalanceCmd(testID, defaultAccount, 2) + }, + result: &GetBalanceCmd{ + id: testID, + Account: &defaultAccount, + MinConf: 2, + }, + }, + { + name: "basic + account + minconf", + cmd: "getbalance", + f: func() (Cmd, error) { + return NewGetBalanceCmd(testID, testAccount, 2) + }, + result: &GetBalanceCmd{ + id: testID, + Account: &testAccount, + MinConf: 2, + }, + }, + { + name: "basic", + cmd: "getbestblockhash", + f: func() (Cmd, error) { + return NewGetBestBlockHashCmd(testID) + }, + result: &GetBestBlockHashCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getblock", + f: func() (Cmd, error) { + return NewGetBlockCmd(testID, + "somehash") + }, + result: &GetBlockCmd{ + id: testID, + Hash: "somehash", + Verbose: true, + }, + }, + { + name: "basic", + cmd: "getblockchaininfo", + f: func() (Cmd, error) { + return NewGetBlockChainInfoCmd(testID) + }, + result: &GetBlockChainInfoCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getblockcount", + f: func() (Cmd, error) { + return NewGetBlockCountCmd(testID) + }, + result: &GetBlockCountCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getblockhash", + f: func() (Cmd, error) { + return NewGetBlockHashCmd(testID, 1234) + }, + result: &GetBlockHashCmd{ + id: testID, + Index: 1234, + }, + }, + { + name: "basic", + cmd: "getblocktemplate", + f: func() (Cmd, error) { + return NewGetBlockTemplateCmd(testID) + }, + result: &GetBlockTemplateCmd{ + id: testID, + }, + }, + { + name: "basic + request", + cmd: "getblocktemplate", + f: func() (Cmd, error) { + return NewGetBlockTemplateCmd(testID, + &TemplateRequest{Mode: "mode", + Capabilities: []string{"one", "two", "three"}}) + }, + result: &GetBlockTemplateCmd{ + id: testID, + Request: &TemplateRequest{ + Mode: "mode", + Capabilities: []string{ + "one", + "two", + "three", + }, + }, + }, + }, + { + name: "basic + request no mode", + cmd: "getblocktemplate", + f: func() (Cmd, error) { + return NewGetBlockTemplateCmd(testID, + &TemplateRequest{ + Capabilities: []string{"one", "two", "three"}}) + }, + result: &GetBlockTemplateCmd{ + id: testID, + Request: &TemplateRequest{ + Capabilities: []string{ + "one", + "two", + "three", + }, + }, + }, + }, + { + name: "basic", + cmd: "getconnectioncount", + f: func() (Cmd, error) { + return NewGetConnectionCountCmd(testID) + }, + result: &GetConnectionCountCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getdifficulty", + f: func() (Cmd, error) { + return NewGetDifficultyCmd(testID) + }, + result: &GetDifficultyCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getgenerate", + f: func() (Cmd, error) { + return NewGetGenerateCmd(testID) + }, + result: &GetGenerateCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "gethashespersec", + f: func() (Cmd, error) { + return NewGetHashesPerSecCmd(testID) + }, + result: &GetHashesPerSecCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getinfo", + f: func() (Cmd, error) { + return NewGetInfoCmd(testID) + }, + result: &GetInfoCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getmininginfo", + f: func() (Cmd, error) { + return NewGetMiningInfoCmd(testID) + }, + result: &GetMiningInfoCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getnettotals", + f: func() (Cmd, error) { + return NewGetNetTotalsCmd(testID) + }, + result: &GetNetTotalsCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getnetworkhashps", + f: func() (Cmd, error) { + return NewGetNetworkHashPSCmd(testID) + }, + result: &GetNetworkHashPSCmd{ + id: testID, + Blocks: 120, + Height: -1, + }, + }, + { + name: "basic + blocks", + cmd: "getnetworkhashps", + f: func() (Cmd, error) { + return NewGetNetworkHashPSCmd(testID, 5000) + }, + result: &GetNetworkHashPSCmd{ + id: testID, + Blocks: 5000, + Height: -1, + }, + }, + { + name: "basic + blocks + height", + cmd: "getnetworkhashps", + f: func() (Cmd, error) { + return NewGetNetworkHashPSCmd(testID, 5000, 1000) + }, + result: &GetNetworkHashPSCmd{ + id: testID, + Blocks: 5000, + Height: 1000, + }, + }, + { + name: "basic", + cmd: "getnetworkinfo", + f: func() (Cmd, error) { + return NewGetNetworkInfoCmd(testID) + }, + result: &GetNetworkInfoCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getnewaddress", + f: func() (Cmd, error) { + return NewGetNewAddressCmd(testID, testAccount) + }, + result: &GetNewAddressCmd{ + id: testID, + Account: testAccount, + }, + }, + { + name: "basic", + cmd: "getpeerinfo", + f: func() (Cmd, error) { + return NewGetPeerInfoCmd(testID) + }, + result: &GetPeerInfoCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getrawchangeaddress", + f: func() (Cmd, error) { + return NewGetRawChangeAddressCmd(testID) + }, + result: &GetRawChangeAddressCmd{ + id: testID, + }, + }, + { + name: "basic + account", + cmd: "getrawchangeaddress", + f: func() (Cmd, error) { + return NewGetRawChangeAddressCmd(testID, + "accountname") + }, + result: &GetRawChangeAddressCmd{ + id: testID, + Account: "accountname", + }, + }, + { + name: "basic", + cmd: "getrawmempool", + f: func() (Cmd, error) { + return NewGetRawMempoolCmd(testID) + }, + result: &GetRawMempoolCmd{ + id: testID, + }, + }, + { + name: "basic noverbose", + cmd: "getrawmempool", + f: func() (Cmd, error) { + return NewGetRawMempoolCmd(testID, false) + }, + result: &GetRawMempoolCmd{ + id: testID, + }, + }, + { + name: "basic verbose", + cmd: "getrawmempool", + f: func() (Cmd, error) { + return NewGetRawMempoolCmd(testID, true) + }, + result: &GetRawMempoolCmd{ + id: testID, + Verbose: true, + }, + }, + { + name: "basic", + cmd: "getrawtransaction", + f: func() (Cmd, error) { + return NewGetRawTransactionCmd(testID, + "sometxid") + }, + result: &GetRawTransactionCmd{ + id: testID, + Txid: "sometxid", + }, + }, + { + name: "basic + verbose", + cmd: "getrawtransaction", + f: func() (Cmd, error) { + return NewGetRawTransactionCmd(testID, + "sometxid", + 1) + }, + result: &GetRawTransactionCmd{ + id: testID, + Txid: "sometxid", + Verbose: 1, + }, + }, + { + name: "basic", + cmd: "getreceivedbyaccount", + f: func() (Cmd, error) { + return NewGetReceivedByAccountCmd(testID, + "abtcaccount", + 1) + }, + result: &GetReceivedByAccountCmd{ + id: testID, + Account: "abtcaccount", + MinConf: 1, + }, + }, + { + name: "basic + opt", + cmd: "getreceivedbyaccount", + f: func() (Cmd, error) { + return NewGetReceivedByAccountCmd(testID, + "abtcaccount", + 2) + }, + result: &GetReceivedByAccountCmd{ + id: testID, + Account: "abtcaccount", + MinConf: 2, + }, + }, + { + name: "basic", + cmd: "getreceivedbyaddress", + f: func() (Cmd, error) { + return NewGetReceivedByAddressCmd(testID, + "abtcaddress", + 1) + }, + result: &GetReceivedByAddressCmd{ + id: testID, + Address: "abtcaddress", + MinConf: 1, + }, + }, + { + name: "basic + opt", + cmd: "getreceivedbyaddress", + f: func() (Cmd, error) { + return NewGetReceivedByAddressCmd(testID, + "abtcaddress", + 2) + }, + result: &GetReceivedByAddressCmd{ + id: testID, + Address: "abtcaddress", + MinConf: 2, + }, + }, + { + name: "basic", + cmd: "gettransaction", + f: func() (Cmd, error) { + return NewGetTransactionCmd(testID, + "atxid") + }, + result: &GetTransactionCmd{ + id: testID, + Txid: "atxid", + }, + }, + { + name: "basic", + cmd: "gettxout", + f: func() (Cmd, error) { + return NewGetTxOutCmd(testID, + "sometx", + 10) + }, + result: &GetTxOutCmd{ + id: testID, + Txid: "sometx", + Output: 10, + IncludeMempool: true, + }, + }, + { + name: "basic + optional", + cmd: "gettxout", + f: func() (Cmd, error) { + return NewGetTxOutCmd(testID, + "sometx", + 10, + false) + }, + result: &GetTxOutCmd{ + id: testID, + Txid: "sometx", + Output: 10, + IncludeMempool: false, + }, + }, + { + name: "basic", + cmd: "gettxoutsetinfo", + f: func() (Cmd, error) { + return NewGetTxOutSetInfoCmd(testID) + }, + result: &GetTxOutSetInfoCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "getwork", + f: func() (Cmd, error) { + return NewGetWorkCmd(testID, "some data") + }, + result: &GetWorkCmd{ + id: testID, + Data: "some data", + }, + }, + { + name: "basic", + cmd: "help", + f: func() (Cmd, error) { + return NewHelpCmd(testID) + }, + result: &HelpCmd{ + id: testID, + }, + }, + { + name: "basic + optional cmd", + cmd: "help", + f: func() (Cmd, error) { + return NewHelpCmd(testID, + "getinfo") + }, + result: &HelpCmd{ + id: testID, + Command: "getinfo", + }, + }, + { + name: "basic", + cmd: "importaddress", + f: func() (Cmd, error) { + return NewImportAddressCmd(testID, + "address") + }, + result: &ImportAddressCmd{ + id: testID, + Address: "address", + Rescan: true, + }, + }, + { + name: "basic + optional cmd", + cmd: "importaddress", + f: func() (Cmd, error) { + return NewImportAddressCmd(testID, + "address", false) + }, + result: &ImportAddressCmd{ + id: testID, + Address: "address", + Rescan: false, + }, + }, + { + name: "basic", + cmd: "importpubkey", + f: func() (Cmd, error) { + return NewImportPubKeyCmd(testID, + "pubkey") + }, + result: &ImportPubKeyCmd{ + id: testID, + PubKey: "pubkey", + Rescan: true, + }, + }, + { + name: "basic + optional cmd", + cmd: "importpubkey", + f: func() (Cmd, error) { + return NewImportPubKeyCmd(testID, + "pubkey", false) + }, + result: &ImportPubKeyCmd{ + id: testID, + PubKey: "pubkey", + Rescan: false, + }, + }, + { + name: "basic", + cmd: "importprivkey", + f: func() (Cmd, error) { + return NewImportPrivKeyCmd(testID, + "somereallongprivatekey") + }, + result: &ImportPrivKeyCmd{ + id: testID, + PrivKey: "somereallongprivatekey", + Rescan: true, + }, + }, + { + name: "basic + 1 opt", + cmd: "importprivkey", + f: func() (Cmd, error) { + return NewImportPrivKeyCmd(testID, + "somereallongprivatekey", + "some text") + }, + result: &ImportPrivKeyCmd{ + id: testID, + PrivKey: "somereallongprivatekey", + Label: "some text", + Rescan: true, + }, + }, + { + name: "basic + 2 opts", + cmd: "importprivkey", + f: func() (Cmd, error) { + return NewImportPrivKeyCmd(testID, + "somereallongprivatekey", + "some text", + false) + }, + result: &ImportPrivKeyCmd{ + id: testID, + PrivKey: "somereallongprivatekey", + Label: "some text", + Rescan: false, + }, + }, + { + name: "basic", + cmd: "importwallet", + f: func() (Cmd, error) { + return NewImportWalletCmd(testID, + "walletfilename.dat") + }, + result: &ImportWalletCmd{ + id: testID, + Filename: "walletfilename.dat", + }, + }, + { + name: "basic", + cmd: "invalidateblock", + f: func() (Cmd, error) { + return NewInvalidateBlockCmd(testID, + "lotsofhex") + }, + result: &InvalidateBlockCmd{ + id: testID, + BlockHash: "lotsofhex", + }, + }, + { + name: "basic", + cmd: "keypoolrefill", + f: func() (Cmd, error) { + return NewKeyPoolRefillCmd(testID) + }, + result: &KeyPoolRefillCmd{ + id: testID, + }, + }, + { + name: "newsize", + cmd: "keypoolrefill", + f: func() (Cmd, error) { + return NewKeyPoolRefillCmd(testID, 1000000) + }, + result: &KeyPoolRefillCmd{ + id: testID, + NewSize: 1000000, + }, + }, + { + name: "basic", + cmd: "listaccounts", + f: func() (Cmd, error) { + return NewListAccountsCmd(testID, 1) + }, + result: &ListAccountsCmd{ + id: testID, + MinConf: 1, + }, + }, + { + name: "basic + opt", + cmd: "listaccounts", + f: func() (Cmd, error) { + return NewListAccountsCmd(testID, 2) + }, + result: &ListAccountsCmd{ + id: testID, + MinConf: 2, + }, + }, + { + name: "basic", + cmd: "listaddressgroupings", + f: func() (Cmd, error) { + return NewListAddressGroupingsCmd(testID) + }, + result: &ListAddressGroupingsCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "listlockunspent", + f: func() (Cmd, error) { + return NewListLockUnspentCmd(testID) + }, + result: &ListLockUnspentCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "listreceivedbyaccount", + f: func() (Cmd, error) { + return NewListReceivedByAccountCmd(testID) + }, + result: &ListReceivedByAccountCmd{ + id: testID, + MinConf: 1, + }, + }, + { + name: "basic + 1 opt", + cmd: "listreceivedbyaccount", + f: func() (Cmd, error) { + return NewListReceivedByAccountCmd(testID, 2) + }, + result: &ListReceivedByAccountCmd{ + id: testID, + MinConf: 2, + }, + }, + { + name: "basic + 2 opt", + cmd: "listreceivedbyaccount", + f: func() (Cmd, error) { + return NewListReceivedByAccountCmd(testID, 2, true) + }, + result: &ListReceivedByAccountCmd{ + id: testID, + MinConf: 2, + IncludeEmpty: true, + }, + }, + { + name: "basic", + cmd: "listreceivedbyaddress", + f: func() (Cmd, error) { + return NewListReceivedByAddressCmd(testID) + }, + result: &ListReceivedByAddressCmd{ + id: testID, + MinConf: 1, + }, + }, + { + name: "basic + 1 opt", + cmd: "listreceivedbyaddress", + f: func() (Cmd, error) { + return NewListReceivedByAddressCmd(testID, 2) + }, + result: &ListReceivedByAddressCmd{ + id: testID, + MinConf: 2, + }, + }, + { + name: "basic + 2 opt", + cmd: "listreceivedbyaddress", + f: func() (Cmd, error) { + return NewListReceivedByAddressCmd(testID, 2, true) + }, + result: &ListReceivedByAddressCmd{ + id: testID, + MinConf: 2, + IncludeEmpty: true, + }, + }, + { + name: "basic", + cmd: "listsinceblock", + f: func() (Cmd, error) { + return NewListSinceBlockCmd(testID) + }, + result: &ListSinceBlockCmd{ + id: testID, + TargetConfirmations: 1, + }, + }, + { + name: "basic + 1 ops", + cmd: "listsinceblock", + f: func() (Cmd, error) { + return NewListSinceBlockCmd(testID, "someblockhash") + }, + result: &ListSinceBlockCmd{ + id: testID, + BlockHash: "someblockhash", + TargetConfirmations: 1, + }, + }, + { + name: "basic + 2 ops", + cmd: "listsinceblock", + f: func() (Cmd, error) { + return NewListSinceBlockCmd(testID, "someblockhash", 3) + }, + result: &ListSinceBlockCmd{ + id: testID, + BlockHash: "someblockhash", + TargetConfirmations: 3, + }, + }, + { + name: "basic", + cmd: "listtransactions", + f: func() (Cmd, error) { + return NewListTransactionsCmd(testID) + }, + result: &ListTransactionsCmd{ + id: testID, + Account: nil, + Count: 10, + From: 0, + }, + }, + { + name: "+ 1 optarg", + cmd: "listtransactions", + f: func() (Cmd, error) { + return NewListTransactionsCmd(testID, testAccount) + }, + result: &ListTransactionsCmd{ + id: testID, + Account: &testAccount, + Count: 10, + From: 0, + }, + }, + { + name: "+ 2 optargs", + cmd: "listtransactions", + f: func() (Cmd, error) { + return NewListTransactionsCmd(testID, testAccount, 123) + }, + result: &ListTransactionsCmd{ + id: testID, + Account: &testAccount, + Count: 123, + From: 0, + }, + }, + { + name: "+ 3 optargs", + cmd: "listtransactions", + f: func() (Cmd, error) { + return NewListTransactionsCmd(testID, testAccount, 123, 456) + }, + result: &ListTransactionsCmd{ + id: testID, + Account: &testAccount, + Count: 123, + From: 456, + }, + }, + { + name: "basic", + cmd: "listunspent", + f: func() (Cmd, error) { + return NewListUnspentCmd(testID) + }, + result: &ListUnspentCmd{ + id: testID, + MinConf: 1, + MaxConf: 999999, + }, + }, + { + name: "basic + opts", + cmd: "listunspent", + f: func() (Cmd, error) { + return NewListUnspentCmd(testID, 0, 6) + }, + result: &ListUnspentCmd{ + id: testID, + MinConf: 0, + MaxConf: 6, + }, + }, + { + name: "basic + opts + addresses", + cmd: "listunspent", + f: func() (Cmd, error) { + return NewListUnspentCmd(testID, 0, 6, []string{ + "a", "b", "c", + }) + }, + result: &ListUnspentCmd{ + id: testID, + MinConf: 0, + MaxConf: 6, + Addresses: []string{"a", "b", "c"}, + }, + }, + { + name: "basic", + cmd: "lockunspent", + f: func() (Cmd, error) { + return NewLockUnspentCmd(testID, true) + }, + result: &LockUnspentCmd{ + id: testID, + Unlock: true, + }, + }, + { + name: "basic", + cmd: "move", + f: func() (Cmd, error) { + return NewMoveCmd(testID, + "account1", + "account2", + 12, + 1) + }, + result: &MoveCmd{ + id: testID, + FromAccount: "account1", + ToAccount: "account2", + Amount: 12, + MinConf: 1, // the default + }, + }, + { + name: "basic + optionals", + cmd: "move", + f: func() (Cmd, error) { + return NewMoveCmd(testID, + "account1", + "account2", + 12, + 1, + "some comment") + }, + result: &MoveCmd{ + id: testID, + FromAccount: "account1", + ToAccount: "account2", + Amount: 12, + MinConf: 1, // the default + Comment: "some comment", + }, + }, + { + name: "basic", + cmd: "ping", + f: func() (Cmd, error) { + return NewPingCmd(testID) + }, + result: &PingCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "reconsiderblock", + f: func() (Cmd, error) { + return NewReconsiderBlockCmd(testID, + "lotsofhex") + }, + result: &ReconsiderBlockCmd{ + id: testID, + BlockHash: "lotsofhex", + }, + }, + { + name: "basic + optionals", + cmd: "searchrawtransactions", + f: func() (Cmd, error) { + return NewSearchRawTransactionsCmd(testID, + "someaddr", 1, 5, 200) + }, + result: &SearchRawTransactionsCmd{ + id: testID, + Address: "someaddr", + Verbose: 1, + Skip: 5, + Count: 200, + }, + }, + { + name: "basic", + cmd: "sendfrom", + f: func() (Cmd, error) { + return NewSendFromCmd(testID, + testAccount, + "address", + 12, + 1) + }, + result: &SendFromCmd{ + id: testID, + FromAccount: testAccount, + ToAddress: "address", + Amount: 12, + MinConf: 1, // the default + }, + }, + { + name: "basic + options", + cmd: "sendfrom", + f: func() (Cmd, error) { + return NewSendFromCmd(testID, + testAccount, + "address", + 12, + 1, + "a comment", + "comment to") + }, + result: &SendFromCmd{ + id: testID, + FromAccount: testAccount, + ToAddress: "address", + Amount: 12, + MinConf: 1, // the default + Comment: "a comment", + CommentTo: "comment to", + }, + }, + { + name: "basic", + cmd: "sendmany", + f: func() (Cmd, error) { + pairs := map[string]int64{ + "address A": 1000, + "address B": 2000, + "address C": 3000, + } + return NewSendManyCmd(testID, + testAccount, + pairs) + }, + result: &SendManyCmd{ + id: testID, + FromAccount: testAccount, + Amounts: map[string]int64{ + "address A": 1000, + "address B": 2000, + "address C": 3000, + }, + MinConf: 1, // the default + }, + }, + { + name: "+ options", + cmd: "sendmany", + f: func() (Cmd, error) { + pairs := map[string]int64{ + "address A": 1000, + "address B": 2000, + "address C": 3000, + } + return NewSendManyCmd(testID, + testAccount, + pairs, + 10, + "comment") + }, + result: &SendManyCmd{ + id: testID, + FromAccount: testAccount, + Amounts: map[string]int64{ + "address A": 1000, + "address B": 2000, + "address C": 3000, + }, + MinConf: 10, + Comment: "comment", + }, + }, + { + name: "basic", + cmd: "sendrawtransaction", + f: func() (Cmd, error) { + return NewSendRawTransactionCmd(testID, + "hexstringofatx") + }, + result: &SendRawTransactionCmd{ + id: testID, + HexTx: "hexstringofatx", + }, + }, + { + name: "allowhighfees: false", + cmd: "sendrawtransaction", + f: func() (Cmd, error) { + return NewSendRawTransactionCmd(testID, + "hexstringofatx", false) + }, + result: &SendRawTransactionCmd{ + id: testID, + HexTx: "hexstringofatx", + }, + }, + { + name: "allowhighfees: true", + cmd: "sendrawtransaction", + f: func() (Cmd, error) { + return NewSendRawTransactionCmd(testID, + "hexstringofatx", true) + }, + result: &SendRawTransactionCmd{ + id: testID, + HexTx: "hexstringofatx", + AllowHighFees: true, + }, + }, + { + name: "basic", + cmd: "sendtoaddress", + f: func() (Cmd, error) { + return NewSendToAddressCmd(testID, + "somebtcaddress", + 1) + }, + result: &SendToAddressCmd{ + id: testID, + Address: "somebtcaddress", + Amount: 1, + }, + }, + { + name: "basic + optional", + cmd: "sendtoaddress", + f: func() (Cmd, error) { + return NewSendToAddressCmd(testID, + "somebtcaddress", + 1, + "a comment", + "comment to") + }, + result: &SendToAddressCmd{ + id: testID, + Address: "somebtcaddress", + Amount: 1, + Comment: "a comment", + CommentTo: "comment to", + }, + }, + { + name: "basic", + cmd: "setaccount", + f: func() (Cmd, error) { + return NewSetAccountCmd(testID, + "somebtcaddress", + "account name") + }, + result: &SetAccountCmd{ + id: testID, + Address: "somebtcaddress", + Account: "account name", + }, + }, + { + name: "basic", + cmd: "setgenerate", + f: func() (Cmd, error) { + return NewSetGenerateCmd(testID, true) + }, + result: &SetGenerateCmd{ + id: testID, + Generate: true, + GenProcLimit: -1, + }, + }, + { + name: "basic + optional", + cmd: "setgenerate", + f: func() (Cmd, error) { + return NewSetGenerateCmd(testID, true, 10) + }, + result: &SetGenerateCmd{ + id: testID, + Generate: true, + GenProcLimit: 10, + }, + }, + { + name: "basic", + cmd: "settxfee", + f: func() (Cmd, error) { + return NewSetTxFeeCmd(testID, 10) + }, + result: &SetTxFeeCmd{ + id: testID, + Amount: 10, + }, + }, + { + name: "basic", + cmd: "signmessage", + f: func() (Cmd, error) { + return NewSignMessageCmd(testID, + "btcaddress", + "a message") + }, + result: &SignMessageCmd{ + id: testID, + Address: "btcaddress", + Message: "a message", + }, + }, + { + name: "basic", + cmd: "signrawtransaction", + f: func() (Cmd, error) { + return NewSignRawTransactionCmd(testID, + "sometxstring") + }, + result: &SignRawTransactionCmd{ + id: testID, + RawTx: "sometxstring", + }, + }, + /* { + name: "basic + optional", + cmd: "signrawtransaction", + f: func() (Cmd, error) { + return NewSignRawTransactionCmd(testID, + "sometxstring", + []RawTxInput{ + RawTxInput{ + Txid: "test", + Vout: 1, + ScriptPubKey: "test", + RedeemScript: "test", + }, + }, + []string{"aprivatekey", "privkey2"}, + "flags") + }, + result: &SignRawTransactionCmd{ + id: testID, + RawTx: "sometxstring", + Inputs: []RawTxInput{ + RawTxInput{ + Txid: "test", + Vout: 1, + ScriptPubKey: "test", + RedeemScript: "test", + }, + }, + PrivKeys: []string{"aprivatekey", "privkey2"}, + Flags: "flags", + }, + },*/ + { + name: "basic", + cmd: "stop", + f: func() (Cmd, error) { + return NewStopCmd(testID) + }, + result: &StopCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "submitblock", + f: func() (Cmd, error) { + return NewSubmitBlockCmd(testID, + "lotsofhex") + }, + result: &SubmitBlockCmd{ + id: testID, + HexBlock: "lotsofhex", + }, + }, + { + name: "+ optional object", + cmd: "submitblock", + f: func() (Cmd, error) { + return NewSubmitBlockCmd(testID, + "lotsofhex", + &SubmitBlockOptions{WorkID: "otherstuff"}) + }, + result: &SubmitBlockCmd{ + id: testID, + HexBlock: "lotsofhex", + Options: &SubmitBlockOptions{WorkID: "otherstuff"}, + }, + }, + { + name: "basic", + cmd: "validateaddress", + f: func() (Cmd, error) { + return NewValidateAddressCmd(testID, + "somebtcaddress") + }, + result: &ValidateAddressCmd{ + id: testID, + Address: "somebtcaddress", + }, + }, + { + name: "basic", + cmd: "verifychain", + f: func() (Cmd, error) { + return NewVerifyChainCmd(testID) + }, + result: &VerifyChainCmd{ + id: testID, + CheckLevel: 3, + CheckDepth: 288, + }, + }, + { + name: "basic + optional", + cmd: "verifychain", + f: func() (Cmd, error) { + return NewVerifyChainCmd(testID, 4, 1) + }, + result: &VerifyChainCmd{ + id: testID, + CheckLevel: 4, + CheckDepth: 1, + }, + }, + { + name: "basic", + cmd: "verifymessage", + f: func() (Cmd, error) { + return NewVerifyMessageCmd(testID, + "someaddress", + "somesig", + "a message") + }, + result: &VerifyMessageCmd{ + id: testID, + Address: "someaddress", + Signature: "somesig", + Message: "a message", + }, + }, + { + name: "basic", + cmd: "walletlock", + f: func() (Cmd, error) { + return NewWalletLockCmd(testID) + }, + result: &WalletLockCmd{ + id: testID, + }, + }, + { + name: "basic", + cmd: "walletpassphrase", + f: func() (Cmd, error) { + return NewWalletPassphraseCmd(testID, + "phrase1", + 10) + }, + result: &WalletPassphraseCmd{ + id: testID, + Passphrase: "phrase1", + Timeout: 10, + }, + }, + { + name: "basic", + cmd: "walletpassphrasechange", + f: func() (Cmd, error) { + return NewWalletPassphraseChangeCmd(testID, + "phrase1", "phrase2") + }, + result: &WalletPassphraseChangeCmd{ + id: testID, + OldPassphrase: "phrase1", + NewPassphrase: "phrase2", + }, + }, +} + +func TestCmds(t *testing.T) { + for _, test := range jsoncmdtests { + c, err := test.f() + name := test.cmd + " " + test.name + if err != nil { + t.Errorf("%s: failed to run func: %v", + name, err) + continue + } + + msg, err := json.Marshal(c) + if err != nil { + t.Errorf("%s: failed to marshal cmd: %v", + name, err) + continue + } + + c2, err := ParseMarshaledCmd(msg) + if err != nil { + t.Errorf("%s: failed to ummarshal cmd: %v", + name, err) + continue + } + + id, ok := (c.Id()).(float64) + if !ok || id != testID { + t.Errorf("%s: id not returned properly", name) + } + + if c.Method() != test.cmd { + t.Errorf("%s: Method name not returned properly: "+ + "%s vs. %s", name, c.Method(), test.cmd) + } + + if !reflect.DeepEqual(test.result, c2) { + t.Errorf("%s: unmarshal not as expected. "+ + "got %v wanted %v", name, spew.Sdump(c2), + spew.Sdump(test.result)) + } + if !reflect.DeepEqual(c, c2) { + t.Errorf("%s: unmarshal not as we started with. "+ + "got %v wanted %v", name, spew.Sdump(c2), + spew.Sdump(c)) + } + } +} + +func TestHelps(t *testing.T) { + helpTests := []string{ + "addmultisigaddress", + "addnode", + "backupwallet", + "createmultisig", + "createrawtransaction", + "debuglevel", + "decoderawtransaction", + "decodescript", + "dumpprivkey", + "dumpwallet", + "encryptwallet", + "estimatefee", + "estimatepriority", + "getaccount", + "getaccountaddress", + "getaddednodeinfo", + "getaddressesbyaccount", + "getbalance", + "getbestblockhash", + "getblock", + "getblockcount", + "getblockhash", + "getblocktemplate", + "getconnectioncount", + "getdifficulty", + "getgenerate", + "gethashespersec", + "getinfo", + "getmininginfo", + "getnettotals", + "getnetworkhashps", + "getnewaddress", + "getpeerinfo", + "getrawchangeaddress", + "getrawmempool", + "getrawtransaction", + "getreceivedbyaccount", + "getreceivedbyaddress", + "gettransaction", + "gettxout", + "gettxoutsetinfo", + "getwork", + "help", + "importprivkey", + "importwallet", + "invalidateblock", + "keypoolrefill", + "listaccounts", + "listaddressgroupings", + "listlockunspent", + "listreceivedbyaccount", + "listreceivedbyaddress", + "listsinceblock", + "listtransactions", + "listunspent", + "lockunspent", + "move", + "ping", + "reconsiderblock", + "searchrawtransactions", + "sendfrom", + "sendmany", + "sendrawtransaction", + "sendtoaddress", + "setaccount", + "setgenerate", + "settxfee", + "signmessage", + "signrawtransaction", + "stop", + "submitblock", + "validateaddress", + "verifychain", + "verifymessage", + "walletlock", + "walletpassphrase", + "walletpassphrasechange", + } + + for _, test := range helpTests { + helpStr, err := GetHelpString(test) + if err != nil { + t.Errorf("%s: failed to get help string: %v", + test, err) + continue + } + + if !strings.HasPrefix(helpStr, test) { + t.Errorf("%s: help string doesn't begin with command "+ + "name: \"%s\"", test, helpStr) + continue + } + } +} diff --git a/btcjson/jsonerr.go b/btcjson/jsonerr.go new file mode 100644 index 00000000..87c07f1e --- /dev/null +++ b/btcjson/jsonerr.go @@ -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", + } +) diff --git a/btcjson/jsonfxns.go b/btcjson/jsonfxns.go new file mode 100644 index 00000000..baa45b65 --- /dev/null +++ b/btcjson/jsonfxns.go @@ -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 := ":" + 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 +} diff --git a/btcjson/jsonfxns_test.go b/btcjson/jsonfxns_test.go new file mode 100644 index 00000000..0398b312 --- /dev/null +++ b/btcjson/jsonfxns_test.go @@ -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: { }" { + 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 +} diff --git a/btcjson/jsonresults.go b/btcjson/jsonresults.go new file mode 100644 index 00000000..54a4a636 --- /dev/null +++ b/btcjson/jsonresults.go @@ -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 +} diff --git a/btcjson/jsonresults_test.go b/btcjson/jsonresults_test.go new file mode 100644 index 00000000..f80b2d18 --- /dev/null +++ b/btcjson/jsonresults_test.go @@ -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 +} diff --git a/btcjson/v2/btcjson/btcdextcmds.go b/btcjson/v2/btcjson/btcdextcmds.go new file mode 100644 index 00000000..a56d7d6c --- /dev/null +++ b/btcjson/v2/btcjson/btcdextcmds.go @@ -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) +} diff --git a/btcjson/v2/btcjson/btcdextcmds_test.go b/btcjson/v2/btcjson/btcdextcmds_test.go new file mode 100644 index 00000000..58ea83cc --- /dev/null +++ b/btcjson/v2/btcjson/btcdextcmds_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/btcwalletextcmds.go b/btcjson/v2/btcjson/btcwalletextcmds.go new file mode 100644 index 00000000..0757e102 --- /dev/null +++ b/btcjson/v2/btcjson/btcwalletextcmds.go @@ -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) +} diff --git a/btcjson/v2/btcjson/btcwalletextcmds_test.go b/btcjson/v2/btcjson/btcwalletextcmds_test.go new file mode 100644 index 00000000..78ad9952 --- /dev/null +++ b/btcjson/v2/btcjson/btcwalletextcmds_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/chainsvrcmds.go b/btcjson/v2/btcjson/chainsvrcmds.go new file mode 100644 index 00000000..47cedafe --- /dev/null +++ b/btcjson/v2/btcjson/chainsvrcmds.go @@ -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) +} diff --git a/btcjson/v2/btcjson/chainsvrcmds_test.go b/btcjson/v2/btcjson/chainsvrcmds_test.go new file mode 100644 index 00000000..4765297b --- /dev/null +++ b/btcjson/v2/btcjson/chainsvrcmds_test.go @@ -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 + } + } + } +} diff --git a/btcjson/v2/btcjson/chainsvrresults.go b/btcjson/v2/btcjson/chainsvrresults.go new file mode 100644 index 00000000..60859f60 --- /dev/null +++ b/btcjson/v2/btcjson/chainsvrresults.go @@ -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"` +} diff --git a/btcjson/v2/btcjson/chainsvrresults_test.go b/btcjson/v2/btcjson/chainsvrresults_test.go new file mode 100644 index 00000000..98d6b1c7 --- /dev/null +++ b/btcjson/v2/btcjson/chainsvrresults_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/chainsvrwscmds.go b/btcjson/v2/btcjson/chainsvrwscmds.go new file mode 100644 index 00000000..aae31d38 --- /dev/null +++ b/btcjson/v2/btcjson/chainsvrwscmds.go @@ -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) +} diff --git a/btcjson/v2/btcjson/chainsvrwscmds_test.go b/btcjson/v2/btcjson/chainsvrwscmds_test.go new file mode 100644 index 00000000..6f9c18e3 --- /dev/null +++ b/btcjson/v2/btcjson/chainsvrwscmds_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/chainsvrwsntfns.go b/btcjson/v2/btcjson/chainsvrwsntfns.go new file mode 100644 index 00000000..8a8ef4b3 --- /dev/null +++ b/btcjson/v2/btcjson/chainsvrwsntfns.go @@ -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) +} diff --git a/btcjson/v2/btcjson/chainsvrwsntfns_test.go b/btcjson/v2/btcjson/chainsvrwsntfns_test.go new file mode 100644 index 00000000..37b03871 --- /dev/null +++ b/btcjson/v2/btcjson/chainsvrwsntfns_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/cmdinfo.go b/btcjson/v2/btcjson/cmdinfo.go new file mode 100644 index 00000000..84799a21 --- /dev/null +++ b/btcjson/v2/btcjson/cmdinfo.go @@ -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 +} diff --git a/btcjson/v2/btcjson/cmdinfo_test.go b/btcjson/v2/btcjson/cmdinfo_test.go new file mode 100644 index 00000000..3e082d88 --- /dev/null +++ b/btcjson/v2/btcjson/cmdinfo_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/cmdparse.go b/btcjson/v2/btcjson/cmdparse.go new file mode 100644 index 00000000..fbd2a919 --- /dev/null +++ b/btcjson/v2/btcjson/cmdparse.go @@ -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 +} diff --git a/btcjson/v2/btcjson/cmdparse_test.go b/btcjson/v2/btcjson/cmdparse_test.go new file mode 100644 index 00000000..84123995 --- /dev/null +++ b/btcjson/v2/btcjson/cmdparse_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/error.go b/btcjson/v2/btcjson/error.go new file mode 100644 index 00000000..6b22e919 --- /dev/null +++ b/btcjson/v2/btcjson/error.go @@ -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} +} diff --git a/btcjson/v2/btcjson/error_test.go b/btcjson/v2/btcjson/error_test.go new file mode 100644 index 00000000..12d9bdf2 --- /dev/null +++ b/btcjson/v2/btcjson/error_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/export_test.go b/btcjson/v2/btcjson/export_test.go new file mode 100644 index 00000000..971a2b4a --- /dev/null +++ b/btcjson/v2/btcjson/export_test.go @@ -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 diff --git a/btcjson/v2/btcjson/help.go b/btcjson/v2/btcjson/help.go new file mode 100644 index 00000000..80a66786 --- /dev/null +++ b/btcjson/v2/btcjson/help.go @@ -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: +// - "--synopsis" Synopsis for the command +// - "-" Description for each command argument +// - "-" Description for each object field +// - "--condition<#>" Description for each result condition +// - "--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 +} diff --git a/btcjson/v2/btcjson/help_test.go b/btcjson/v2/btcjson/help_test.go new file mode 100644 index 00000000..1756c5bb --- /dev/null +++ b/btcjson/v2/btcjson/help_test.go @@ -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) + } +} diff --git a/btcjson/v2/btcjson/helpers.go b/btcjson/v2/btcjson/helpers.go new file mode 100644 index 00000000..a3d66a10 --- /dev/null +++ b/btcjson/v2/btcjson/helpers.go @@ -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 +} diff --git a/btcjson/v2/btcjson/helpers_test.go b/btcjson/v2/btcjson/helpers_test.go new file mode 100644 index 00000000..cb55a9b2 --- /dev/null +++ b/btcjson/v2/btcjson/helpers_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/jsonrpc.go b/btcjson/v2/btcjson/jsonrpc.go new file mode 100644 index 00000000..f356b0e3 --- /dev/null +++ b/btcjson/v2/btcjson/jsonrpc.go @@ -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 NewCmd 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) +} diff --git a/btcjson/v2/btcjson/jsonrpc_test.go b/btcjson/v2/btcjson/jsonrpc_test.go new file mode 100644 index 00000000..8a48462e --- /dev/null +++ b/btcjson/v2/btcjson/jsonrpc_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/jsonrpcerr.go b/btcjson/v2/btcjson/jsonrpcerr.go new file mode 100644 index 00000000..8ed3bad7 --- /dev/null +++ b/btcjson/v2/btcjson/jsonrpcerr.go @@ -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 +) diff --git a/btcjson/v2/btcjson/register.go b/btcjson/v2/btcjson/register.go new file mode 100644 index 00000000..ac18ce0b --- /dev/null +++ b/btcjson/v2/btcjson/register.go @@ -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 +} diff --git a/btcjson/v2/btcjson/register_test.go b/btcjson/v2/btcjson/register_test.go new file mode 100644 index 00000000..f0131075 --- /dev/null +++ b/btcjson/v2/btcjson/register_test.go @@ -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") + } +} diff --git a/btcjson/v2/btcjson/walletsvrcmds.go b/btcjson/v2/btcjson/walletsvrcmds.go new file mode 100644 index 00000000..bafbbacd --- /dev/null +++ b/btcjson/v2/btcjson/walletsvrcmds.go @@ -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) +} diff --git a/btcjson/v2/btcjson/walletsvrcmds_test.go b/btcjson/v2/btcjson/walletsvrcmds_test.go new file mode 100644 index 00000000..d68820a6 --- /dev/null +++ b/btcjson/v2/btcjson/walletsvrcmds_test.go @@ -0,0 +1,1250 @@ +// 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" +) + +// TestWalletSvrCmds tests all of the wallet 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 TestWalletSvrCmds(t *testing.T) { + t.Parallel() + + testID := int(1) + tests := []struct { + name string + newCmd func() (interface{}, error) + staticCmd func() interface{} + marshalled string + unmarshalled interface{} + }{ + { + name: "addmultisigaddress", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("addmultisigaddress", 2, []string{"031234", "035678"}) + }, + staticCmd: func() interface{} { + keys := []string{"031234", "035678"} + return btcjson.NewAddMultisigAddressCmd(2, keys, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"addmultisigaddress","params":[2,["031234","035678"]],"id":1}`, + unmarshalled: &btcjson.AddMultisigAddressCmd{ + NRequired: 2, + Keys: []string{"031234", "035678"}, + Account: btcjson.String(""), + }, + }, + { + name: "addmultisigaddress optional", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("addmultisigaddress", 2, []string{"031234", "035678"}, "test") + }, + staticCmd: func() interface{} { + keys := []string{"031234", "035678"} + return btcjson.NewAddMultisigAddressCmd(2, keys, btcjson.String("test")) + }, + marshalled: `{"jsonrpc":"1.0","method":"addmultisigaddress","params":[2,["031234","035678"],"test"],"id":1}`, + unmarshalled: &btcjson.AddMultisigAddressCmd{ + NRequired: 2, + Keys: []string{"031234", "035678"}, + Account: btcjson.String("test"), + }, + }, + { + name: "createmultisig", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("createmultisig", 2, []string{"031234", "035678"}) + }, + staticCmd: func() interface{} { + keys := []string{"031234", "035678"} + return btcjson.NewCreateMultisigCmd(2, keys) + }, + marshalled: `{"jsonrpc":"1.0","method":"createmultisig","params":[2,["031234","035678"]],"id":1}`, + unmarshalled: &btcjson.CreateMultisigCmd{ + NRequired: 2, + Keys: []string{"031234", "035678"}, + }, + }, + { + name: "dumpprivkey", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("dumpprivkey", "1Address") + }, + staticCmd: func() interface{} { + return btcjson.NewDumpPrivKeyCmd("1Address") + }, + marshalled: `{"jsonrpc":"1.0","method":"dumpprivkey","params":["1Address"],"id":1}`, + unmarshalled: &btcjson.DumpPrivKeyCmd{ + Address: "1Address", + }, + }, + { + name: "encryptwallet", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("encryptwallet", "pass") + }, + staticCmd: func() interface{} { + return btcjson.NewEncryptWalletCmd("pass") + }, + marshalled: `{"jsonrpc":"1.0","method":"encryptwallet","params":["pass"],"id":1}`, + unmarshalled: &btcjson.EncryptWalletCmd{ + Passphrase: "pass", + }, + }, + { + name: "estimatefee", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("estimatefee", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewEstimateFeeCmd(6) + }, + marshalled: `{"jsonrpc":"1.0","method":"estimatefee","params":[6],"id":1}`, + unmarshalled: &btcjson.EstimateFeeCmd{ + NumBlocks: 6, + }, + }, + { + name: "estimatepriority", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("estimatepriority", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewEstimatePriorityCmd(6) + }, + marshalled: `{"jsonrpc":"1.0","method":"estimatepriority","params":[6],"id":1}`, + unmarshalled: &btcjson.EstimatePriorityCmd{ + NumBlocks: 6, + }, + }, + { + name: "getaccount", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getaccount", "1Address") + }, + staticCmd: func() interface{} { + return btcjson.NewGetAccountCmd("1Address") + }, + marshalled: `{"jsonrpc":"1.0","method":"getaccount","params":["1Address"],"id":1}`, + unmarshalled: &btcjson.GetAccountCmd{ + Address: "1Address", + }, + }, + { + name: "getaccountaddress", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getaccountaddress", "acct") + }, + staticCmd: func() interface{} { + return btcjson.NewGetAccountAddressCmd("acct") + }, + marshalled: `{"jsonrpc":"1.0","method":"getaccountaddress","params":["acct"],"id":1}`, + unmarshalled: &btcjson.GetAccountAddressCmd{ + Account: "acct", + }, + }, + { + name: "getaddressesbyaccount", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getaddressesbyaccount", "acct") + }, + staticCmd: func() interface{} { + return btcjson.NewGetAddressesByAccountCmd("acct") + }, + marshalled: `{"jsonrpc":"1.0","method":"getaddressesbyaccount","params":["acct"],"id":1}`, + unmarshalled: &btcjson.GetAddressesByAccountCmd{ + Account: "acct", + }, + }, + { + name: "getbalance", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getbalance") + }, + staticCmd: func() interface{} { + return btcjson.NewGetBalanceCmd(nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":[],"id":1}`, + unmarshalled: &btcjson.GetBalanceCmd{ + Account: btcjson.String("*"), + MinConf: btcjson.Int(1), + }, + }, + { + name: "getbalance optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getbalance", "acct") + }, + staticCmd: func() interface{} { + return btcjson.NewGetBalanceCmd(btcjson.String("acct"), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct"],"id":1}`, + unmarshalled: &btcjson.GetBalanceCmd{ + Account: btcjson.String("acct"), + MinConf: btcjson.Int(1), + }, + }, + { + name: "getbalance optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getbalance", "acct", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewGetBalanceCmd(btcjson.String("acct"), btcjson.Int(6)) + }, + marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct",6],"id":1}`, + unmarshalled: &btcjson.GetBalanceCmd{ + Account: btcjson.String("acct"), + MinConf: btcjson.Int(6), + }, + }, + { + name: "getnewaddress", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getnewaddress") + }, + staticCmd: func() interface{} { + return btcjson.NewGetNewAddressCmd(nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":[],"id":1}`, + unmarshalled: &btcjson.GetNewAddressCmd{ + Account: btcjson.String(""), + }, + }, + { + name: "getnewaddress optional", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getnewaddress", "acct") + }, + staticCmd: func() interface{} { + return btcjson.NewGetNewAddressCmd(btcjson.String("acct")) + }, + marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":["acct"],"id":1}`, + unmarshalled: &btcjson.GetNewAddressCmd{ + Account: btcjson.String("acct"), + }, + }, + { + name: "getrawchangeaddress", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getrawchangeaddress") + }, + staticCmd: func() interface{} { + return btcjson.NewGetRawChangeAddressCmd(nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":[],"id":1}`, + unmarshalled: &btcjson.GetRawChangeAddressCmd{ + Account: btcjson.String(""), + }, + }, + { + name: "getrawchangeaddress optional", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getrawchangeaddress", "acct") + }, + staticCmd: func() interface{} { + return btcjson.NewGetRawChangeAddressCmd(btcjson.String("acct")) + }, + marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":["acct"],"id":1}`, + unmarshalled: &btcjson.GetRawChangeAddressCmd{ + Account: btcjson.String("acct"), + }, + }, + { + name: "getreceivedbyaccount", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getreceivedbyaccount", "acct") + }, + staticCmd: func() interface{} { + return btcjson.NewGetReceivedByAccountCmd("acct", nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct"],"id":1}`, + unmarshalled: &btcjson.GetReceivedByAccountCmd{ + Account: "acct", + MinConf: btcjson.Int(1), + }, + }, + { + name: "getreceivedbyaccount optional", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getreceivedbyaccount", "acct", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewGetReceivedByAccountCmd("acct", btcjson.Int(6)) + }, + marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct",6],"id":1}`, + unmarshalled: &btcjson.GetReceivedByAccountCmd{ + Account: "acct", + MinConf: btcjson.Int(6), + }, + }, + { + name: "getreceivedbyaddress", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getreceivedbyaddress", "1Address") + }, + staticCmd: func() interface{} { + return btcjson.NewGetReceivedByAddressCmd("1Address", nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaddress","params":["1Address"],"id":1}`, + unmarshalled: &btcjson.GetReceivedByAddressCmd{ + Address: "1Address", + MinConf: btcjson.Int(1), + }, + }, + { + name: "getreceivedbyaddress optional", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("getreceivedbyaddress", "1Address", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewGetReceivedByAddressCmd("1Address", btcjson.Int(6)) + }, + marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaddress","params":["1Address",6],"id":1}`, + unmarshalled: &btcjson.GetReceivedByAddressCmd{ + Address: "1Address", + MinConf: btcjson.Int(6), + }, + }, + { + name: "gettransaction", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("gettransaction", "123") + }, + staticCmd: func() interface{} { + return btcjson.NewGetTransactionCmd("123", nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"gettransaction","params":["123"],"id":1}`, + unmarshalled: &btcjson.GetTransactionCmd{ + Txid: "123", + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "gettransaction optional", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("gettransaction", "123", true) + }, + staticCmd: func() interface{} { + return btcjson.NewGetTransactionCmd("123", btcjson.Bool(true)) + }, + marshalled: `{"jsonrpc":"1.0","method":"gettransaction","params":["123",true],"id":1}`, + unmarshalled: &btcjson.GetTransactionCmd{ + Txid: "123", + IncludeWatchOnly: btcjson.Bool(true), + }, + }, + { + name: "importprivkey", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("importprivkey", "abc") + }, + staticCmd: func() interface{} { + return btcjson.NewImportPrivKeyCmd("abc", nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"importprivkey","params":["abc"],"id":1}`, + unmarshalled: &btcjson.ImportPrivKeyCmd{ + PrivKey: "abc", + Label: btcjson.String(""), + Rescan: btcjson.Bool(true), + }, + }, + { + name: "importprivkey optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("importprivkey", "abc", "label") + }, + staticCmd: func() interface{} { + return btcjson.NewImportPrivKeyCmd("abc", btcjson.String("label"), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"importprivkey","params":["abc","label"],"id":1}`, + unmarshalled: &btcjson.ImportPrivKeyCmd{ + PrivKey: "abc", + Label: btcjson.String("label"), + Rescan: btcjson.Bool(true), + }, + }, + { + name: "importprivkey optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("importprivkey", "abc", "label", false) + }, + staticCmd: func() interface{} { + return btcjson.NewImportPrivKeyCmd("abc", btcjson.String("label"), btcjson.Bool(false)) + }, + marshalled: `{"jsonrpc":"1.0","method":"importprivkey","params":["abc","label",false],"id":1}`, + unmarshalled: &btcjson.ImportPrivKeyCmd{ + PrivKey: "abc", + Label: btcjson.String("label"), + Rescan: btcjson.Bool(false), + }, + }, + { + name: "keypoolrefill", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("keypoolrefill") + }, + staticCmd: func() interface{} { + return btcjson.NewKeyPoolRefillCmd(nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"keypoolrefill","params":[],"id":1}`, + unmarshalled: &btcjson.KeyPoolRefillCmd{ + NewSize: btcjson.Uint(100), + }, + }, + { + name: "keypoolrefill optional", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("keypoolrefill", 200) + }, + staticCmd: func() interface{} { + return btcjson.NewKeyPoolRefillCmd(btcjson.Uint(200)) + }, + marshalled: `{"jsonrpc":"1.0","method":"keypoolrefill","params":[200],"id":1}`, + unmarshalled: &btcjson.KeyPoolRefillCmd{ + NewSize: btcjson.Uint(200), + }, + }, + { + name: "listaccounts", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listaccounts") + }, + staticCmd: func() interface{} { + return btcjson.NewListAccountsCmd(nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[],"id":1}`, + unmarshalled: &btcjson.ListAccountsCmd{ + MinConf: btcjson.Int(1), + }, + }, + { + name: "listaccounts optional", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listaccounts", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewListAccountsCmd(btcjson.Int(6)) + }, + marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[6],"id":1}`, + unmarshalled: &btcjson.ListAccountsCmd{ + MinConf: btcjson.Int(6), + }, + }, + { + name: "listaddressgroupings", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listaddressgroupings") + }, + staticCmd: func() interface{} { + return btcjson.NewListAddressGroupingsCmd() + }, + marshalled: `{"jsonrpc":"1.0","method":"listaddressgroupings","params":[],"id":1}`, + unmarshalled: &btcjson.ListAddressGroupingsCmd{}, + }, + { + name: "listlockunspent", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listlockunspent") + }, + staticCmd: func() interface{} { + return btcjson.NewListLockUnspentCmd() + }, + marshalled: `{"jsonrpc":"1.0","method":"listlockunspent","params":[],"id":1}`, + unmarshalled: &btcjson.ListLockUnspentCmd{}, + }, + { + name: "listreceivedbyaccount", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listreceivedbyaccount") + }, + staticCmd: func() interface{} { + return btcjson.NewListReceivedByAccountCmd(nil, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listreceivedbyaccount","params":[],"id":1}`, + unmarshalled: &btcjson.ListReceivedByAccountCmd{ + MinConf: btcjson.Int(1), + IncludeEmpty: btcjson.Bool(false), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listreceivedbyaccount optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listreceivedbyaccount", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewListReceivedByAccountCmd(btcjson.Int(6), nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listreceivedbyaccount","params":[6],"id":1}`, + unmarshalled: &btcjson.ListReceivedByAccountCmd{ + MinConf: btcjson.Int(6), + IncludeEmpty: btcjson.Bool(false), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listreceivedbyaccount optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listreceivedbyaccount", 6, true) + }, + staticCmd: func() interface{} { + return btcjson.NewListReceivedByAccountCmd(btcjson.Int(6), btcjson.Bool(true), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listreceivedbyaccount","params":[6,true],"id":1}`, + unmarshalled: &btcjson.ListReceivedByAccountCmd{ + MinConf: btcjson.Int(6), + IncludeEmpty: btcjson.Bool(true), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listreceivedbyaccount optional3", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listreceivedbyaccount", 6, true, false) + }, + staticCmd: func() interface{} { + return btcjson.NewListReceivedByAccountCmd(btcjson.Int(6), btcjson.Bool(true), btcjson.Bool(false)) + }, + marshalled: `{"jsonrpc":"1.0","method":"listreceivedbyaccount","params":[6,true,false],"id":1}`, + unmarshalled: &btcjson.ListReceivedByAccountCmd{ + MinConf: btcjson.Int(6), + IncludeEmpty: btcjson.Bool(true), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listreceivedbyaddress", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listreceivedbyaddress") + }, + staticCmd: func() interface{} { + return btcjson.NewListReceivedByAddressCmd(nil, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listreceivedbyaddress","params":[],"id":1}`, + unmarshalled: &btcjson.ListReceivedByAddressCmd{ + MinConf: btcjson.Int(1), + IncludeEmpty: btcjson.Bool(false), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listreceivedbyaddress optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listreceivedbyaddress", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewListReceivedByAddressCmd(btcjson.Int(6), nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listreceivedbyaddress","params":[6],"id":1}`, + unmarshalled: &btcjson.ListReceivedByAddressCmd{ + MinConf: btcjson.Int(6), + IncludeEmpty: btcjson.Bool(false), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listreceivedbyaddress optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listreceivedbyaddress", 6, true) + }, + staticCmd: func() interface{} { + return btcjson.NewListReceivedByAddressCmd(btcjson.Int(6), btcjson.Bool(true), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listreceivedbyaddress","params":[6,true],"id":1}`, + unmarshalled: &btcjson.ListReceivedByAddressCmd{ + MinConf: btcjson.Int(6), + IncludeEmpty: btcjson.Bool(true), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listreceivedbyaddress optional3", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listreceivedbyaddress", 6, true, false) + }, + staticCmd: func() interface{} { + return btcjson.NewListReceivedByAddressCmd(btcjson.Int(6), btcjson.Bool(true), btcjson.Bool(false)) + }, + marshalled: `{"jsonrpc":"1.0","method":"listreceivedbyaddress","params":[6,true,false],"id":1}`, + unmarshalled: &btcjson.ListReceivedByAddressCmd{ + MinConf: btcjson.Int(6), + IncludeEmpty: btcjson.Bool(true), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listsinceblock", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listsinceblock") + }, + staticCmd: func() interface{} { + return btcjson.NewListSinceBlockCmd(nil, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listsinceblock","params":[],"id":1}`, + unmarshalled: &btcjson.ListSinceBlockCmd{ + BlockHash: nil, + TargetConfirmations: btcjson.Int(1), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listsinceblock optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listsinceblock", "123") + }, + staticCmd: func() interface{} { + return btcjson.NewListSinceBlockCmd(btcjson.String("123"), nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listsinceblock","params":["123"],"id":1}`, + unmarshalled: &btcjson.ListSinceBlockCmd{ + BlockHash: btcjson.String("123"), + TargetConfirmations: btcjson.Int(1), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listsinceblock optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listsinceblock", "123", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewListSinceBlockCmd(btcjson.String("123"), btcjson.Int(6), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listsinceblock","params":["123",6],"id":1}`, + unmarshalled: &btcjson.ListSinceBlockCmd{ + BlockHash: btcjson.String("123"), + TargetConfirmations: btcjson.Int(6), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listsinceblock optional3", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listsinceblock", "123", 6, true) + }, + staticCmd: func() interface{} { + return btcjson.NewListSinceBlockCmd(btcjson.String("123"), btcjson.Int(6), btcjson.Bool(true)) + }, + marshalled: `{"jsonrpc":"1.0","method":"listsinceblock","params":["123",6,true],"id":1}`, + unmarshalled: &btcjson.ListSinceBlockCmd{ + BlockHash: btcjson.String("123"), + TargetConfirmations: btcjson.Int(6), + IncludeWatchOnly: btcjson.Bool(true), + }, + }, + { + name: "listtransactions", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listtransactions") + }, + staticCmd: func() interface{} { + return btcjson.NewListTransactionsCmd(nil, nil, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":[],"id":1}`, + unmarshalled: &btcjson.ListTransactionsCmd{ + Account: nil, + Count: btcjson.Int(10), + From: btcjson.Int(0), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listtransactions optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listtransactions", "acct") + }, + staticCmd: func() interface{} { + return btcjson.NewListTransactionsCmd(btcjson.String("acct"), nil, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":["acct"],"id":1}`, + unmarshalled: &btcjson.ListTransactionsCmd{ + Account: btcjson.String("acct"), + Count: btcjson.Int(10), + From: btcjson.Int(0), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listtransactions optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listtransactions", "acct", 20) + }, + staticCmd: func() interface{} { + return btcjson.NewListTransactionsCmd(btcjson.String("acct"), btcjson.Int(20), nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":["acct",20],"id":1}`, + unmarshalled: &btcjson.ListTransactionsCmd{ + Account: btcjson.String("acct"), + Count: btcjson.Int(20), + From: btcjson.Int(0), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listtransactions optional3", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listtransactions", "acct", 20, 1) + }, + staticCmd: func() interface{} { + return btcjson.NewListTransactionsCmd(btcjson.String("acct"), btcjson.Int(20), + btcjson.Int(1), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":["acct",20,1],"id":1}`, + unmarshalled: &btcjson.ListTransactionsCmd{ + Account: btcjson.String("acct"), + Count: btcjson.Int(20), + From: btcjson.Int(1), + IncludeWatchOnly: btcjson.Bool(false), + }, + }, + { + name: "listtransactions optional4", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listtransactions", "acct", 20, 1, true) + }, + staticCmd: func() interface{} { + return btcjson.NewListTransactionsCmd(btcjson.String("acct"), btcjson.Int(20), + btcjson.Int(1), btcjson.Bool(true)) + }, + marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":["acct",20,1,true],"id":1}`, + unmarshalled: &btcjson.ListTransactionsCmd{ + Account: btcjson.String("acct"), + Count: btcjson.Int(20), + From: btcjson.Int(1), + IncludeWatchOnly: btcjson.Bool(true), + }, + }, + { + name: "listunspent", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listunspent") + }, + staticCmd: func() interface{} { + return btcjson.NewListUnspentCmd(nil, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listunspent","params":[],"id":1}`, + unmarshalled: &btcjson.ListUnspentCmd{ + MinConf: btcjson.Int(1), + MaxConf: btcjson.Int(9999999), + Addresses: nil, + }, + }, + { + name: "listunspent optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listunspent", 6) + }, + staticCmd: func() interface{} { + return btcjson.NewListUnspentCmd(btcjson.Int(6), nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listunspent","params":[6],"id":1}`, + unmarshalled: &btcjson.ListUnspentCmd{ + MinConf: btcjson.Int(6), + MaxConf: btcjson.Int(9999999), + Addresses: nil, + }, + }, + { + name: "listunspent optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listunspent", 6, 100) + }, + staticCmd: func() interface{} { + return btcjson.NewListUnspentCmd(btcjson.Int(6), btcjson.Int(100), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"listunspent","params":[6,100],"id":1}`, + unmarshalled: &btcjson.ListUnspentCmd{ + MinConf: btcjson.Int(6), + MaxConf: btcjson.Int(100), + Addresses: nil, + }, + }, + { + name: "listunspent optional3", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("listunspent", 6, 100, []string{"1Address", "1Address2"}) + }, + staticCmd: func() interface{} { + return btcjson.NewListUnspentCmd(btcjson.Int(6), btcjson.Int(100), + &[]string{"1Address", "1Address2"}) + }, + marshalled: `{"jsonrpc":"1.0","method":"listunspent","params":[6,100,["1Address","1Address2"]],"id":1}`, + unmarshalled: &btcjson.ListUnspentCmd{ + MinConf: btcjson.Int(6), + MaxConf: btcjson.Int(100), + Addresses: &[]string{"1Address", "1Address2"}, + }, + }, + { + name: "lockunspent", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("lockunspent", true, `[{"txid":"123","vout":1}]`) + }, + staticCmd: func() interface{} { + txInputs := []btcjson.TransactionInput{ + {Txid: "123", Vout: 1}, + } + return btcjson.NewLockUnspentCmd(true, txInputs) + }, + marshalled: `{"jsonrpc":"1.0","method":"lockunspent","params":[true,[{"txid":"123","vout":1}]],"id":1}`, + unmarshalled: &btcjson.LockUnspentCmd{ + Unlock: true, + Transactions: []btcjson.TransactionInput{ + {Txid: "123", Vout: 1}, + }, + }, + }, + { + name: "move", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("move", "from", "to", 0.5) + }, + staticCmd: func() interface{} { + return btcjson.NewMoveCmd("from", "to", 0.5, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"move","params":["from","to",0.5],"id":1}`, + unmarshalled: &btcjson.MoveCmd{ + FromAccount: "from", + ToAccount: "to", + Amount: 0.5, + MinConf: btcjson.Int(1), + Comment: nil, + }, + }, + { + name: "move optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("move", "from", "to", 0.5, 6) + }, + staticCmd: func() interface{} { + return btcjson.NewMoveCmd("from", "to", 0.5, btcjson.Int(6), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"move","params":["from","to",0.5,6],"id":1}`, + unmarshalled: &btcjson.MoveCmd{ + FromAccount: "from", + ToAccount: "to", + Amount: 0.5, + MinConf: btcjson.Int(6), + Comment: nil, + }, + }, + { + name: "move optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("move", "from", "to", 0.5, 6, "comment") + }, + staticCmd: func() interface{} { + return btcjson.NewMoveCmd("from", "to", 0.5, btcjson.Int(6), btcjson.String("comment")) + }, + marshalled: `{"jsonrpc":"1.0","method":"move","params":["from","to",0.5,6,"comment"],"id":1}`, + unmarshalled: &btcjson.MoveCmd{ + FromAccount: "from", + ToAccount: "to", + Amount: 0.5, + MinConf: btcjson.Int(6), + Comment: btcjson.String("comment"), + }, + }, + { + name: "sendfrom", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5) + }, + staticCmd: func() interface{} { + return btcjson.NewSendFromCmd("from", "1Address", 0.5, nil, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5],"id":1}`, + unmarshalled: &btcjson.SendFromCmd{ + FromAccount: "from", + ToAddress: "1Address", + Amount: 0.5, + MinConf: btcjson.Int(1), + Comment: nil, + CommentTo: nil, + }, + }, + { + name: "sendfrom optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6) + }, + staticCmd: func() interface{} { + return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6],"id":1}`, + unmarshalled: &btcjson.SendFromCmd{ + FromAccount: "from", + ToAddress: "1Address", + Amount: 0.5, + MinConf: btcjson.Int(6), + Comment: nil, + CommentTo: nil, + }, + }, + { + name: "sendfrom optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment") + }, + staticCmd: func() interface{} { + return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), + btcjson.String("comment"), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment"],"id":1}`, + unmarshalled: &btcjson.SendFromCmd{ + FromAccount: "from", + ToAddress: "1Address", + Amount: 0.5, + MinConf: btcjson.Int(6), + Comment: btcjson.String("comment"), + CommentTo: nil, + }, + }, + { + name: "sendfrom optional3", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment", "commentto") + }, + staticCmd: func() interface{} { + return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), + btcjson.String("comment"), btcjson.String("commentto")) + }, + marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment","commentto"],"id":1}`, + unmarshalled: &btcjson.SendFromCmd{ + FromAccount: "from", + ToAddress: "1Address", + Amount: 0.5, + MinConf: btcjson.Int(6), + Comment: btcjson.String("comment"), + CommentTo: btcjson.String("commentto"), + }, + }, + { + name: "sendmany", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`) + }, + staticCmd: func() interface{} { + amounts := map[string]float64{"1Address": 0.5} + return btcjson.NewSendManyCmd("from", amounts, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5}],"id":1}`, + unmarshalled: &btcjson.SendManyCmd{ + FromAccount: "from", + Amounts: map[string]float64{"1Address": 0.5}, + MinConf: btcjson.Int(1), + Comment: nil, + }, + }, + { + name: "sendmany optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6) + }, + staticCmd: func() interface{} { + amounts := map[string]float64{"1Address": 0.5} + return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6],"id":1}`, + unmarshalled: &btcjson.SendManyCmd{ + FromAccount: "from", + Amounts: map[string]float64{"1Address": 0.5}, + MinConf: btcjson.Int(6), + Comment: nil, + }, + }, + { + name: "sendmany optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "comment") + }, + staticCmd: func() interface{} { + amounts := map[string]float64{"1Address": 0.5} + return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("comment")) + }, + marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"comment"],"id":1}`, + unmarshalled: &btcjson.SendManyCmd{ + FromAccount: "from", + Amounts: map[string]float64{"1Address": 0.5}, + MinConf: btcjson.Int(6), + Comment: btcjson.String("comment"), + }, + }, + { + name: "sendtoaddress", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("sendtoaddress", "1Address", 0.5) + }, + staticCmd: func() interface{} { + return btcjson.NewSendToAddressCmd("1Address", 0.5, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5],"id":1}`, + unmarshalled: &btcjson.SendToAddressCmd{ + Address: "1Address", + Amount: 0.5, + Comment: nil, + CommentTo: nil, + }, + }, + { + name: "sendtoaddress optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "comment", "commentto") + }, + staticCmd: func() interface{} { + return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("comment"), + btcjson.String("commentto")) + }, + marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"comment","commentto"],"id":1}`, + unmarshalled: &btcjson.SendToAddressCmd{ + Address: "1Address", + Amount: 0.5, + Comment: btcjson.String("comment"), + CommentTo: btcjson.String("commentto"), + }, + }, + { + name: "setaccount", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("setaccount", "1Address", "acct") + }, + staticCmd: func() interface{} { + return btcjson.NewSetAccountCmd("1Address", "acct") + }, + marshalled: `{"jsonrpc":"1.0","method":"setaccount","params":["1Address","acct"],"id":1}`, + unmarshalled: &btcjson.SetAccountCmd{ + Address: "1Address", + Account: "acct", + }, + }, + { + name: "settxfee", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("settxfee", 0.0001) + }, + staticCmd: func() interface{} { + return btcjson.NewSetTxFeeCmd(0.0001) + }, + marshalled: `{"jsonrpc":"1.0","method":"settxfee","params":[0.0001],"id":1}`, + unmarshalled: &btcjson.SetTxFeeCmd{ + Amount: 0.0001, + }, + }, + { + name: "signmessage", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signmessage", "1Address", "message") + }, + staticCmd: func() interface{} { + return btcjson.NewSignMessageCmd("1Address", "message") + }, + marshalled: `{"jsonrpc":"1.0","method":"signmessage","params":["1Address","message"],"id":1}`, + unmarshalled: &btcjson.SignMessageCmd{ + Address: "1Address", + Message: "message", + }, + }, + { + name: "signrawtransaction", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransaction", "001122") + }, + staticCmd: func() interface{} { + return btcjson.NewSignRawTransactionCmd("001122", nil, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransaction","params":["001122"],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionCmd{ + RawTx: "001122", + Inputs: nil, + PrivKeys: nil, + Flags: btcjson.String("ALL"), + }, + }, + { + name: "signrawtransaction optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransaction", "001122", `[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01"}]`) + }, + staticCmd: func() interface{} { + txInputs := []btcjson.RawTxInput{ + { + Txid: "123", + Vout: 1, + ScriptPubKey: "00", + RedeemScript: "01", + }, + } + + return btcjson.NewSignRawTransactionCmd("001122", &txInputs, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransaction","params":["001122",[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01"}]],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionCmd{ + RawTx: "001122", + Inputs: &[]btcjson.RawTxInput{ + { + Txid: "123", + Vout: 1, + ScriptPubKey: "00", + RedeemScript: "01", + }, + }, + PrivKeys: nil, + Flags: btcjson.String("ALL"), + }, + }, + { + name: "signrawtransaction optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransaction", "001122", `[]`, `["abc"]`) + }, + staticCmd: func() interface{} { + txInputs := []btcjson.RawTxInput{} + privKeys := []string{"abc"} + return btcjson.NewSignRawTransactionCmd("001122", &txInputs, &privKeys, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransaction","params":["001122",[],["abc"]],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionCmd{ + RawTx: "001122", + Inputs: &[]btcjson.RawTxInput{}, + PrivKeys: &[]string{"abc"}, + Flags: btcjson.String("ALL"), + }, + }, + { + name: "signrawtransaction optional3", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransaction", "001122", `[]`, `[]`, "ALL") + }, + staticCmd: func() interface{} { + txInputs := []btcjson.RawTxInput{} + privKeys := []string{} + return btcjson.NewSignRawTransactionCmd("001122", &txInputs, &privKeys, + btcjson.String("ALL")) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransaction","params":["001122",[],[],"ALL"],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionCmd{ + RawTx: "001122", + Inputs: &[]btcjson.RawTxInput{}, + PrivKeys: &[]string{}, + Flags: btcjson.String("ALL"), + }, + }, + { + name: "walletlock", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("walletlock") + }, + staticCmd: func() interface{} { + return btcjson.NewWalletLockCmd() + }, + marshalled: `{"jsonrpc":"1.0","method":"walletlock","params":[],"id":1}`, + unmarshalled: &btcjson.WalletLockCmd{}, + }, + { + name: "walletpassphrase", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("walletpassphrase", "pass", 60) + }, + staticCmd: func() interface{} { + return btcjson.NewWalletPassphraseCmd("pass", 60) + }, + marshalled: `{"jsonrpc":"1.0","method":"walletpassphrase","params":["pass",60],"id":1}`, + unmarshalled: &btcjson.WalletPassphraseCmd{ + Passphrase: "pass", + Timeout: 60, + }, + }, + { + name: "walletpassphrasechange", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("walletpassphrasechange", "old", "new") + }, + staticCmd: func() interface{} { + return btcjson.NewWalletPassphraseChangeCmd("old", "new") + }, + marshalled: `{"jsonrpc":"1.0","method":"walletpassphrasechange","params":["old","new"],"id":1}`, + unmarshalled: &btcjson.WalletPassphraseChangeCmd{ + OldPassphrase: "old", + NewPassphrase: "new", + }, + }, + } + + 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 + } + } +} diff --git a/btcjson/v2/btcjson/walletsvrresults.go b/btcjson/v2/btcjson/walletsvrresults.go new file mode 100644 index 00000000..0025ccae --- /dev/null +++ b/btcjson/v2/btcjson/walletsvrresults.go @@ -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"` +} diff --git a/btcjson/v2/btcjson/walletsvrwscmds.go b/btcjson/v2/btcjson/walletsvrwscmds.go new file mode 100644 index 00000000..ea7dd850 --- /dev/null +++ b/btcjson/v2/btcjson/walletsvrwscmds.go @@ -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) +} diff --git a/btcjson/v2/btcjson/walletsvrwscmds_test.go b/btcjson/v2/btcjson/walletsvrwscmds_test.go new file mode 100644 index 00000000..a4556c0a --- /dev/null +++ b/btcjson/v2/btcjson/walletsvrwscmds_test.go @@ -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 + } + } +} diff --git a/btcjson/v2/btcjson/walletsvrwsntfns.go b/btcjson/v2/btcjson/walletsvrwsntfns.go new file mode 100644 index 00000000..fc58070b --- /dev/null +++ b/btcjson/v2/btcjson/walletsvrwsntfns.go @@ -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) +} diff --git a/btcjson/v2/btcjson/walletsvrwsntfns_test.go b/btcjson/v2/btcjson/walletsvrwsntfns_test.go new file mode 100644 index 00000000..ef8b28ec --- /dev/null +++ b/btcjson/v2/btcjson/walletsvrwsntfns_test.go @@ -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 + } + } +}