diff --git a/internal/rpchelp/genrpcserverhelp.go b/internal/rpchelp/genrpcserverhelp.go new file mode 100644 index 0000000..de2dbbf --- /dev/null +++ b/internal/rpchelp/genrpcserverhelp.go @@ -0,0 +1,99 @@ +// Copyright (c) 2015 Conformal Systems LLC +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +//+build generate + +package main + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/btcsuite/btcd/btcjson/v2/btcjson" + "github.com/btcsuite/btcwallet/internal/rpchelp" +) + +var outputFile = func() *os.File { + fi, err := os.Create("rpcserverhelp.go") + if err != nil { + log.Fatal(err) + } + return fi +}() + +func writefln(format string, args ...interface{}) { + _, err := fmt.Fprintf(outputFile, format, args...) + if err != nil { + log.Fatal(err) + } + _, err = outputFile.Write([]byte{'\n'}) + if err != nil { + log.Fatal(err) + } +} + +func writeLocaleHelp(locale, goLocale string, descs map[string]string) { + funcName := "helpDescs" + goLocale + writefln("func %s() map[string]string {", funcName) + writefln("return map[string]string{") + for i := range rpchelp.Methods { + m := &rpchelp.Methods[i] + helpText, err := btcjson.GenerateHelp(m.Method, descs, m.ResultTypes...) + if err != nil { + log.Fatal(err) + } + writefln("%q: %q,", m.Method, helpText) + } + writefln("}") + writefln("}") +} + +func writeLocales() { + writefln("var localeHelpDescs = map[string]func() map[string]string{") + for _, h := range rpchelp.HelpDescs { + writefln("%q: helpDescs%s,", h.Locale, h.GoLocale) + } + writefln("}") +} + +func writeUsage() { + usageStrs := make([]string, len(rpchelp.Methods)) + var err error + for i := range rpchelp.Methods { + usageStrs[i], err = btcjson.MethodUsageText(rpchelp.Methods[i].Method) + if err != nil { + log.Fatal(err) + } + } + usages := strings.Join(usageStrs, "\n") + writefln("var requestUsages = %q", usages) +} + +func main() { + defer outputFile.Close() + + writefln("// AUTOGENERATED by internal/rpchelp/genrpcserverhelp.go; do not edit.") + writefln("") + writefln("package main") + writefln("") + for _, h := range rpchelp.HelpDescs { + writeLocaleHelp(h.Locale, h.GoLocale, h.Descs) + writefln("") + } + writeLocales() + writefln("") + writeUsage() +} diff --git a/internal/rpchelp/helpdescs_en_US.go b/internal/rpchelp/helpdescs_en_US.go new file mode 100644 index 0000000..3c16bf9 --- /dev/null +++ b/internal/rpchelp/helpdescs_en_US.go @@ -0,0 +1,399 @@ +// Copyright (c) 2015 Conformal Systems LLC +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +//+build !generate + +package rpchelp + +var helpDescsEnUS = map[string]string{ + // AddMultisigAddressCmd help. + "addmultisigaddress--synopsis": "Generates and imports a multisig address and redeeming script to the 'imported' account.", + "addmultisigaddress-account": "DEPRECATED -- Unused (all imported addresses belong to the imported account)", + "addmultisigaddress-keys": "Pubkeys and/or pay-to-pubkey-hash addresses to partially control the multisig address", + "addmultisigaddress-nrequired": "The number of signatures required to redeem outputs paid to this address", + "addmultisigaddress--result0": "The imported pay-to-script-hash address", + + // CreateMultisigCmd help. + "createmultisig--synopsis": "Generate a multisig address and redeem script.", + "createmultisig-keys": "Pubkeys and/or pay-to-pubkey-hash addresses to partially control the multisig address", + "createmultisig-nrequired": "The number of signatures required to redeem outputs paid to this address", + + // CreateMultisigResult help. + "createmultisigresult-address": "The generated pay-to-script-hash address", + "createmultisigresult-redeemScript": "The script required to redeem outputs paid to the multisig address", + + // DumpPrivKeyCmd help. + "dumpprivkey--synopsis": "Returns the private key in WIF encoding that controls some wallet address.", + "dumpprivkey-address": "The address to return a private key for", + "dumpprivkey--result0": "The WIF-encoded private key", + + // GetAccountCmd help. + "getaccount--synopsis": "DEPRECATED -- Lookup the account name that some wallet address belongs to.", + "getaccount-address": "The address to query the account for", + "getaccount--result0": "The name of the account that 'address' belongs to", + + // GetAccountAddressCmd help. + "getaccountaddress--synopsis": "DEPRECATED -- Returns the most recent external payment address for an account that has not been seen publicly.\n" + + "A new address is generated for the account if the most recently generated address has been seen on the blockchain or in mempool.", + "getaccountaddress-account": "The account of the returned address", + "getaccountaddress--result0": "The unused address for 'account'", + + // GetAddressesByAccountCmd help. + "getaddressesbyaccount--synopsis": "DEPRECATED -- Returns all addresses strings controlled by a single account.", + "getaddressesbyaccount-account": "Account name to fetch addresses for", + "getaddressesbyaccount--result0": "All addresses controlled by 'account'", + + // GetBalanceCmd help. + "getbalance--synopsis": "Calculates and returns the balance of one or all accounts.", + "getbalance-minconf": "Minimum number of block confirmations required before an unspent output's value is included in the balance", + "getbalance-account": "DEPRECATED -- The account name to query the balance for, or '*' to consider all accounts", + "getbalance--condition0": "account!=*", + "getbalance--condition1": "account=*", + "getbalance--result0": "The balance of 'account' valued in bitcoin", + "getbalance--result1": "The balance of all accounts valued in bitcoin", + + // GetBestBlockHashCmd help. + "getbestblockhash--synopsis": "Returns the hash of the newest block in the best chain that wallet has finished syncing with.", + "getbestblockhash--result0": "The hash of the most recent synced-to block", + + // GetBlockCountCmd help. + "getblockcount--synopsis": "Returns the blockchain height of the newest block in the best chain that wallet has finished syncing with.", + "getblockcount--result0": "The blockchain height of the most recent synced-to block", + + // GetInfoCmd help. + "getinfo--synopsis": "Returns a JSON object containing various state info.", + + // InfoWalletResult help. + "infowalletresult-version": "The version of the server", + "infowalletresult-protocolversion": "The latest supported protocol version", + "infowalletresult-blocks": "The number of blocks processed", + "infowalletresult-timeoffset": "The time offset", + "infowalletresult-connections": "The number of connected peers", + "infowalletresult-proxy": "The proxy used by the server", + "infowalletresult-difficulty": "The current target difficulty", + "infowalletresult-testnet": "Whether or not server is using testnet", + "infowalletresult-relayfee": "The minimum relay fee for non-free transactions in BTC/KB", + "infowalletresult-errors": "Any current errors", + "infowalletresult-paytxfee": "The increment used each time more fee is required for an authored transaction", + "infowalletresult-balance": "The balance of all accounts calculated with one block confirmation", + "infowalletresult-walletversion": "The version of the address manager database", + "infowalletresult-unlocked_until": "Unset", + "infowalletresult-keypoolsize": "Unset", + "infowalletresult-keypoololdest": "Unset", + + // GetNewAddressCmd help. + "getnewaddress--synopsis": "Generates and returns a new payment address.", + "getnewaddress-account": "DEPRECATED -- Account name the new address will belong to", + "getnewaddress--result0": "The payment address", + + // GetRawChangeAddressCmd help. + "getrawchangeaddress--synopsis": "Generates and returns a new internal payment address for use as a change address in raw transactions.", + "getrawchangeaddress-account": "Account name the new internal address will belong to", + "getrawchangeaddress--result0": "The internal payment address", + + // GetReceivedByAccountCmd help. + "getreceivedbyaccount--synopsis": "DEPRECATED -- Returns the total amount received by addresses of some account, including spent outputs.", + "getreceivedbyaccount-account": "Account name to query total received amount for", + "getreceivedbyaccount-minconf": "Minimum number of block confirmations required before an output's value is included in the total", + "getreceivedbyaccount--result0": "The total received amount valued in bitcoin", + + // GetReceivedByAddressCmd help. + "getreceivedbyaddress--synopsis": "Returns the total amount received by a single address, including spent outputs.", + "getreceivedbyaddress-address": "Payment address which received outputs to include in total", + "getreceivedbyaddress-minconf": "Minimum number of block confirmations required before an output's value is included in the total", + "getreceivedbyaddress--result0": "The total received amount valued in bitcoin", + + // GetTransactionCmd help. + "gettransaction--synopsis": "Returns a JSON object with details regarding a transaction relevant to this wallet.", + "gettransaction-txid": "Hash of the transaction to query", + "gettransaction-includewatchonly": "Also consider transactions involving watched addresses", + + // HelpCmd help. + "help--synopsis": "Returns a list of all commands or help for a specified command.", + "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", + + // GetTransactionResult help. + "gettransactionresult-amount": "The total amount this transaction credits to the wallet, valued in bitcoin", + "gettransactionresult-fee": "The total input value minus the total output value, or 0 if 'txid' is not a sent transaction", + "gettransactionresult-confirmations": "The number of block confirmations of the transaction", + "gettransactionresult-blockhash": "The hash of the block this transaction is mined in, or the empty string if unmined", + "gettransactionresult-blockindex": "Unset", + "gettransactionresult-blocktime": "The Unix time of the block header this transaction is mined in, or 0 if unmined", + "gettransactionresult-txid": "The transaction hash", + "gettransactionresult-walletconflicts": "Unset", + "gettransactionresult-time": "The earliest Unix time this transaction was known to exist", + "gettransactionresult-timereceived": "The earliest Unix time this transaction was known to exist", + "gettransactionresult-details": "Additional details for each recorded wallet credit and debit", + "gettransactionresult-hex": "The transaction encoded as a hexadecimal string", + + // GetTransactionDetailsResult help. + "gettransactiondetailsresult-account": "DEPRECATED -- Unset", + "gettransactiondetailsresult-address": "The address an output was paid to, or the empty string if the output is nonstandard or this detail is regarding a transaction input", + "gettransactiondetailsresult-category": `The kind of detail: "send" for sent transactions, "immature" for immature coinbase outputs, "generate" for mature coinbase outputs, or "recv" for all other received outputs`, + "gettransactiondetailsresult-amount": "The amount of a received output", + "gettransactiondetailsresult-fee": "The included fee for a sent transaction", + + // ImportPrivKeyCmd help. + "importprivkey--synopsis": "Imports a WIF-encoded private key to the 'imported' account.", + "importprivkey-privkey": "The WIF-encoded private key", + "importprivkey-label": "Unused (all imported addresses belong to the imported account)", + "importprivkey-rescan": "Rescan the blockchain (since the genesis block) for outputs controlled by the imported key", + + // KeypoolRefillCmd help. + "keypoolrefill--synopsis": "DEPRECATED -- This request does nothing since no keypool is maintained.", + "keypoolrefill-newsize": "Unused", + + // ListAccountsCmd help. + "listaccounts--synopsis": "DEPRECATED -- Returns a JSON object of all accounts and their balances.", + "listaccounts-minconf": "Minimum number of block confirmations required before an unspent output's value is included in the balance", + "listaccounts--result0--desc": "JSON object with account names as keys and bitcoin amounts as values", + "listaccounts--result0--key": "The account name", + "listaccounts--result0--value": "The account balance valued in bitcoin", + + // ListLockUnspentCmd help. + "listlockunspent--synopsis": "Returns a JSON array of outpoints marked as locked (with lockunspent) for this wallet session.", + + // TransactionInput help. + "transactioninput-txid": "The transaction hash of the referenced output", + "transactioninput-vout": "The output index of the referenced output", + + // ListReceivedByAccountCmd help. + "listreceivedbyaccount--synopsis": "DEPRECATED -- Returns a JSON array of objects listing all accounts and the total amount received by each account.", + "listreceivedbyaccount-minconf": "Minimum number of block confirmations required before a transaction is considered", + "listreceivedbyaccount-includeempty": "Unused", + "listreceivedbyaccount-includewatchonly": "Unused", + + // ListReceivedByAccountResult help. + "listreceivedbyaccountresult-account": "The name of the account", + "listreceivedbyaccountresult-amount": "Total amount received by payment addresses of the account valued in bitcoin", + "listreceivedbyaccountresult-confirmations": "Number of block confirmations of the most recent transaction relevant to the account", + + // ListReceivedByAddressCmd help. + "listreceivedbyaddress--synopsis": "Returns a JSON array of objects listing wallet payment addresses and their total received amounts.", + "listreceivedbyaddress-minconf": "Minimum number of block confirmations required before a transaction is considered", + "listreceivedbyaddress-includeempty": "Unused", + "listreceivedbyaddress-includewatchonly": "Unused", + + // ListReceivedByAddressResult help. + "listreceivedbyaddressresult-account": "DEPRECATED -- Unset", + "listreceivedbyaddressresult-address": "The payment address", + "listreceivedbyaddressresult-amount": "Total amount received by the payment address valued in bitcoin", + "listreceivedbyaddressresult-confirmations": "Number of block confirmations of the most recent transaction relevant to the address", + "listreceivedbyaddressresult-txids": "Transaction hashes of all transactions involving this address", + "listreceivedbyaddressresult-involvesWatchonly": "Unset", + + // ListSinceBlockCmd help. + "listsinceblock--synopsis": "Returns a JSON array of objects listing details of wallet transactions after some block.", + "listsinceblock-blockhash": "Hash of the parent block of the first block to consider transactions from", + "listsinceblock-targetconfirmations": "Minimum number of block confirmations required before a transaction is considered", + "listsinceblock-includewatchonly": "Unused", + + // ListSinceBlockResult help. + "listsinceblockresult-transactions": "JSON array of objects containing verbose details of the each transaction", + "listsinceblockresult-lastblock": "Hash of the latest-synced block to be used in later calls to listsinceblock", + + // ListTransactionsResult help. + "listtransactionsresult-account": "DEPRECATED -- Unset", + "listtransactionsresult-address": "Payment address for a transaction output", + "listtransactionsresult-category": `The kind of transaction: "send" for sent transactions, "immature" for immature coinbase outputs, "generate" for mature coinbase outputs, or "recv" for all other received outputs. Note: A single output may be included multiple times under different categories`, + "listtransactionsresult-amount": "The value of the transaction output valued in bitcoin", + "listtransactionsresult-fee": "The total input value minus the total output value for sent transactions", + "listtransactionsresult-confirmations": "The number of block confirmations of the transaction", + "listtransactionsresult-generated": "Whether the transaction output is a coinbase output", + "listtransactionsresult-blockhash": "The hash of the block this transaction is mined in, or the empty string if unmined", + "listtransactionsresult-blockindex": "Unset", + "listtransactionsresult-blocktime": "The Unix time of the block header this transaction is mined in, or 0 if unmined", + "listtransactionsresult-txid": "The hash of the transaction", + "listtransactionsresult-walletconflicts": "Unset", + "listtransactionsresult-time": "The earliest Unix time this transaction was known to exist", + "listtransactionsresult-timereceived": "The earliest Unix time this transaction was known to exist", + "listtransactionsresult-comment": "Unset", + "listtransactionsresult-otheraccount": "Unset", + + // ListTransactionsCmd help. + "listtransactions--synopsis": "Returns a JSON array of objects containing verbose details for wallet transactions.", + "listtransactions-account": "DEPRECATED -- Unused", + "listtransactions-count": "Maximum number of transactions to create results from", + "listtransactions-from": "Number of transactions to skip before results are created", + "listtransactions-includewatchonly": "Unused", + + // ListUnspentCmd help. + "listunspent--synopsis": "Returns a JSON array of objects representing unlocked unspent outputs controlled by wallet keys.", + "listunspent-minconf": "Minimum number of block confirmations required before a transaction output is considered", + "listunspent-maxconf": "Maximum number of block confirmations required before a transaction output is excluded", + "listunspent-addresses": "If set, limits the returned details to unspent outputs received by any of these payment addresses", + + // ListUnspentResult help. + "listunspentresult-txid": "The transaction hash of the referenced output", + "listunspentresult-vout": "The output index of the referenced output", + "listunspentresult-address": "The payment address that received the output", + "listunspentresult-account": "The account associated with the receiving payment address", + "listunspentresult-scriptPubKey": "The output script encoded as a hexadecimal string", + "listunspentresult-redeemScript": "Unset", + "listunspentresult-amount": "The amount of the output valued in bitcoin", + "listunspentresult-confirmations": "The number of block confirmations of the transaction", + + // LockUnspentCmd help. + "lockunspent--synopsis": "Locks or unlocks an unspent output.\n" + + "Locked outputs are not chosen for transaction inputs of authored transactions and are not included in 'listunspent' results.\n" + + "Locked outputs are volatile and are not saved across wallet restarts.\n" + + "If unlock is true and no transaction outputs are specified, all locked outputs are marked unlocked.", + "lockunspent-unlock": "True to unlock outputs, false to lock", + "lockunspent-transactions": "Transaction outputs to lock or unlock", + "lockunspent--result0": "The boolean 'true'", + + // SendFromCmd help. + "sendfrom--synopsis": "DEPRECATED -- Authors, signs, and sends a transaction that outputs some amount to a payment address.\n" + + "A change output is automatically included to send extra output value back to the original account.", + "sendfrom-fromaccount": "Account to pick unspent outputs from", + "sendfrom-toaddress": "Address to pay", + "sendfrom-amount": "Amount to send to the payment address valued in bitcoin", + "sendfrom-minconf": "Minimum number of block confirmations required before a transaction output is eligible to be spent", + "sendfrom-comment": "Unused", + "sendfrom-commentto": "Unused", + "sendfrom--result0": "The transaction hash of the sent transaction", + + // SendManyCmd help. + "sendmany--synopsis": "Authors, signs, and sends a transaction that outputs to many payment addresses.\n" + + "A change output is automatically included to send extra output value back to the original account.", + "sendmany-fromaccount": "DEPRECATED -- Account to pick unspent outputs from", + "sendmany-amounts": "Pairs of payment addresses and the output amount to pay each", + "sendmany-amounts--desc": "JSON object using payment addresses as keys and output amounts valued in bitcoin to send to each address", + "sendmany-amounts--key": "Address to pay", + "sendmany-amounts--value": "Amount to send to the payment address valued in bitcoin", + "sendmany-minconf": "Minimum number of block confirmations required before a transaction output is eligible to be spent", + "sendmany-comment": "Unused", + "sendmany--result0": "The transaction hash of the sent transaction", + + // SendToAddressCmd help. + "sendtoaddress--synopsis": "Authors, signs, and sends a transaction that outputs some amount to a payment address.\n" + + "Unlike sendfrom, outputs are always chosen from the default account.\n" + + "A change output is automatically included to send extra output value back to the original account.", + "sendtoaddress-address": "Address to pay", + "sendtoaddress-amount": "Amount to send to the payment address valued in bitcoin", + "sendtoaddress-comment": "Unused", + "sendtoaddress-commentto": "Unused", + "sendtoaddress--result0": "The transaction hash of the sent transaction", + + // SetTxFeeCmd help. + "settxfee--synopsis": "Modify the increment used each time more fee is required for an authored transaction.", + "settxfee-amount": "The new fee increment valued in bitcoin", + "settxfee--result0": "The boolean 'true'", + + // SignMessageCmd help. + "signmessage--synopsis": "Signs a message using the private key of a payment address.", + "signmessage-address": "Payment address of private key used to sign the message with", + "signmessage-message": "Message to sign", + "signmessage--result0": "The signed message encoded as a base64 string", + + // SignRawTransactionCmd help. + "signrawtransaction--synopsis": "Signs transaction inputs using private keys from this wallet and request.\n" + + "The valid flags options are ALL, NONE, SINGLE, ALL|ANYONECANPAY, NONE|ANYONECANPAY, and SINGLE|ANYONECANPAY.", + "signrawtransaction-rawtx": "Unsigned or partially unsigned transaction to sign encoded as a hexadecimal string", + "signrawtransaction-inputs": "Additional data regarding inputs that this wallet may not be tracking", + "signrawtransaction-privkeys": "Additional WIF-encoded private keys to use when creating signatures", + "signrawtransaction-flags": "Sighash flags", + + // SignRawTransactionResult help. + "signrawtransactionresult-hex": "The resulting transaction encoded as a hexadecimal string", + "signrawtransactionresult-complete": "Whether all input signatures have been created", + + // ValidateAddressCmd help. + "validateaddress--synopsis": "Verify that an address is valid.\n" + + "Extra details are returned if the address is controlled by this wallet.\n" + + "The following fields are valid only when the address is controlled by this wallet (ismine=true): isscript, pubkey, iscompressed, account, addresses, hex, script, and sigsrequired.\n" + + "The following fields are only valid when address has an associated public key: pubkey, iscompressed.\n" + + "The following fields are only valid when address is a pay-to-script-hash address: addresses, hex, and script.\n" + + "If the address is a multisig address controlled by this wallet, the multisig fields will be left unset if the wallet is locked since the redeem script cannot be decrypted.", + "validateaddress-address": "Address to validate", + + // ValidateAddressWalletResult help. + "validateaddresswalletresult-isvalid": "Whether or not the address is valid", + "validateaddresswalletresult-address": "The payment address (only when isvalid is true)", + "validateaddresswalletresult-ismine": "Whether this address is controlled by the wallet (only when isvalid is true)", + "validateaddresswalletresult-iswatchonly": "Unset", + "validateaddresswalletresult-isscript": "Whether the payment address is a pay-to-script-hash address (only when isvalid is true)", + "validateaddresswalletresult-pubkey": "The associated public key of the payment address, if any (only when isvalid is true)", + "validateaddresswalletresult-iscompressed": "Whether the address was created by hashing a compressed public key, if any (only when isvalid is true)", + "validateaddresswalletresult-account": "The account this payment address belongs to (only when isvalid is true)", + "validateaddresswalletresult-addresses": "All associated payment addresses of the script if address is a multisig address (only when isvalid is true)", + "validateaddresswalletresult-hex": "The redeem script ", + "validateaddresswalletresult-script": "The class of redeem script for a multisig address", + "validateaddresswalletresult-sigsrequired": "The number of required signatures to redeem outputs to the multisig address", + + // VerifyMessageCmd help. + "verifymessage--synopsis": "Verify a message was signed with the associated private key of some address.", + "verifymessage-address": "Address used to sign message", + "verifymessage-signature": "The signature to verify", + "verifymessage-message": "The message to verify", + "verifymessage--result0": "Whether the message was signed with the private key of 'address'", + + // WalletLockCmd help. + "walletlock--synopsis": "Lock the wallet.", + + // WalletPassphraseCmd help. + "walletpassphrase--synopsis": "Unlock the wallet.", + "walletpassphrase-passphrase": "The wallet passphrase", + "walletpassphrase-timeout": "The number of seconds to wait before the wallet automatically locks", + + // WalletPassphraseChangeCmd help. + "walletpassphrasechange--synopsis": "Change the wallet passphrase.", + "walletpassphrasechange-oldpassphrase": "The old wallet passphrase", + "walletpassphrasechange-newpassphrase": "The new wallet passphrase", + + // CreateNewAccountCmd help. + "createnewaccount--synopsis": "Creates a new account.\n" + + "The wallet must be unlocked for this request to succeed.", + "createnewaccount-account": "Name of the new account", + + // ExportWatchingWalletCmd help. + "exportwatchingwallet--synopsis": "Creates and returns a duplicate of the wallet database without any private keys to be used as a watching-only wallet.", + "exportwatchingwallet-account": "Unused", + "exportwatchingwallet-download": "Unused", + "exportwatchingwallet--result0": "The watching-only database encoded as a base64 string", + + // GetBestBlockCmd help. + "getbestblock--synopsis": "Returns the hash and height of the newest block in the best chain that wallet has finished syncing with.", + + // GetBestBlockResult help. + "getbestblockresult-hash": "The hash of the block", + "getbestblockresult-height": "The blockchain height of the block", + + // GetUnconfirmedBalanceCmd help. + "getunconfirmedbalance--synopsis": "Calculates the unspent output value of all unmined transaction outputs for an account.", + "getunconfirmedbalance-account": "The account to query the unconfirmed balance for", + "getunconfirmedbalance--result0": "Total amount of all unmined unspent outputs of the account valued in bitcoin.", + + // ListAddressTransactionsCmd help. + "listaddresstransactions--synopsis": "Returns a JSON array of objects containing verbose details for wallet transactions pertaining some addresses.", + "listaddresstransactions-addresses": "Addresses to filter transaction results by", + "listaddresstransactions-account": "Unused", + + // ListAllTransactionsCmd help. + "listalltransactions--synopsis": "Returns a JSON array of objects in the same format as 'listtransactions' without limiting the number of returned objects.", + "listalltransactions-account": "Unused", + + // RenameAccountCmd help. + "renameaccount--synopsis": "Renames an account.", + "renameaccount-oldaccount": "The old account name to rename", + "renameaccount-newaccount": "The new name for the account", + + // WalletIsLockedCmd help. + "walletislocked--synopsis": "Returns whether or not the wallet is locked.", + "walletislocked--result0": "Whether the wallet is locked", +} diff --git a/internal/rpchelp/methods.go b/internal/rpchelp/methods.go new file mode 100644 index 0000000..85eac56 --- /dev/null +++ b/internal/rpchelp/methods.go @@ -0,0 +1,89 @@ +// Copyright (c) 2015 Conformal Systems LLC +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +//+build !generate + +package rpchelp + +import "github.com/btcsuite/btcd/btcjson/v2/btcjson" + +// Common return types. +var ( + returnsBool = []interface{}{(*bool)(nil)} + returnsNumber = []interface{}{(*float64)(nil)} + returnsString = []interface{}{(*string)(nil)} + returnsStringArray = []interface{}{(*[]string)(nil)} + returnsLTRArray = []interface{}{(*[]btcjson.ListTransactionsResult)(nil)} +) + +// Contains all methods and result types that help is generated for, for every +// locale. +var Methods = []struct { + Method string + ResultTypes []interface{} +}{ + {"addmultisigaddress", returnsString}, + {"createmultisig", []interface{}{(*btcjson.CreateMultiSigResult)(nil)}}, + {"dumpprivkey", returnsString}, + {"getaccount", returnsString}, + {"getaccountaddress", returnsString}, + {"getaddressesbyaccount", returnsStringArray}, + {"getbalance", append(returnsNumber, returnsNumber[0])}, + {"getbestblockhash", returnsString}, + {"getblockcount", returnsNumber}, + {"getinfo", []interface{}{(*btcjson.InfoWalletResult)(nil)}}, + {"getnewaddress", returnsString}, + {"getrawchangeaddress", returnsString}, + {"getreceivedbyaccount", returnsNumber}, + {"getreceivedbyaddress", returnsNumber}, + {"gettransaction", []interface{}{(*btcjson.GetTransactionResult)(nil)}}, + {"help", append(returnsString, returnsString[0])}, + {"importprivkey", nil}, + {"keypoolrefill", nil}, + {"listaccounts", []interface{}{(*map[string]float64)(nil)}}, + {"listlockunspent", []interface{}{(*[]btcjson.TransactionInput)(nil)}}, + {"listreceivedbyaccount", []interface{}{(*[]btcjson.ListReceivedByAccountResult)(nil)}}, + {"listreceivedbyaddress", []interface{}{(*[]btcjson.ListReceivedByAddressResult)(nil)}}, + {"listsinceblock", []interface{}{(*btcjson.ListSinceBlockResult)(nil)}}, + {"listtransactions", returnsLTRArray}, + {"listunspent", []interface{}{(*btcjson.ListUnspentResult)(nil)}}, + {"lockunspent", returnsBool}, + {"sendfrom", returnsString}, + {"sendmany", returnsString}, + {"sendtoaddress", returnsString}, + {"settxfee", returnsBool}, + {"signmessage", returnsString}, + {"signrawtransaction", []interface{}{(*btcjson.SignRawTransactionResult)(nil)}}, + {"validateaddress", []interface{}{(*btcjson.ValidateAddressWalletResult)(nil)}}, + {"verifymessage", returnsBool}, + {"walletlock", nil}, + {"walletpassphrase", nil}, + {"walletpassphrasechange", nil}, + {"createnewaccount", nil}, + {"exportwatchingwallet", returnsString}, + {"getbestblock", []interface{}{(*btcjson.GetBestBlockResult)(nil)}}, + {"getunconfirmedbalance", returnsNumber}, + {"listaddresstransactions", returnsLTRArray}, + {"listalltransactions", returnsLTRArray}, + {"renameaccount", nil}, + {"walletislocked", returnsBool}, +} + +var HelpDescs = []struct { + Locale string // Actual locale, e.g. en_US + GoLocale string // Locale used in Go names, e.g. EnUS + Descs map[string]string +}{ + {"en_US", "EnUS", helpDescsEnUS}, // helpdescs_en_US.go +} diff --git a/rpchelp_test.go b/rpchelp_test.go new file mode 100644 index 0000000..3022717 --- /dev/null +++ b/rpchelp_test.go @@ -0,0 +1,109 @@ +// Copyright (c) 2015 Conformal Systems LLC +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package main + +import ( + "strings" + "testing" + + "github.com/btcsuite/btcd/btcjson/v2/btcjson" + "github.com/btcsuite/btcwallet/internal/rpchelp" +) + +func serverMethods() map[string]struct{} { + m := make(map[string]struct{}) + for method, handlerData := range rpcHandlers { + if !handlerData.noHelp { + m[method] = struct{}{} + } + } + return m +} + +// TestRPCMethodHelpGeneration ensures that help text can be generated for every +// method of the RPC server for every supported locale. +func TestRPCMethodHelpGeneration(t *testing.T) { + needsGenerate := false + + defer func() { + if needsGenerate && !t.Failed() { + t.Error("Generated help texts are out of date: run 'go generate'") + return + } + if t.Failed() { + t.Log("Regenerate help texts with 'go generate' after fixing") + } + }() + + for i := range rpchelp.HelpDescs { + svrMethods := serverMethods() + locale := rpchelp.HelpDescs[i].Locale + generatedDescs := localeHelpDescs[locale]() + for _, m := range rpchelp.Methods { + delete(svrMethods, m.Method) + + helpText, err := btcjson.GenerateHelp(m.Method, rpchelp.HelpDescs[i].Descs, m.ResultTypes...) + if err != nil { + t.Errorf("Cannot generate '%s' help for method '%s': missing description for '%s'", + locale, m.Method, err) + continue + } + if !needsGenerate && helpText != generatedDescs[m.Method] { + needsGenerate = true + } + } + + for m := range svrMethods { + t.Errorf("Missing '%s' help for method '%s'", locale, m) + } + } +} + +// TestRPCMethodUsageGeneration ensures that single line usage text can be +// generated for every supported request of the RPC server. +func TestRPCMethodUsageGeneration(t *testing.T) { + needsGenerate := false + + defer func() { + if needsGenerate && !t.Failed() { + t.Error("Generated help usages are out of date: run 'go generate'") + return + } + if t.Failed() { + t.Log("Regenerate help usage with 'go generate' after fixing") + } + }() + + svrMethods := serverMethods() + usageStrs := make([]string, 0, len(rpchelp.Methods)) + for _, m := range rpchelp.Methods { + delete(svrMethods, m.Method) + + usage, err := btcjson.MethodUsageText(m.Method) + if err != nil { + t.Errorf("Cannot generate single line usage for method '%s': %v", + m.Method, err) + } + + if !t.Failed() { + usageStrs = append(usageStrs, usage) + } + } + + if !t.Failed() { + usages := strings.Join(usageStrs, "\n") + needsGenerate = usages != requestUsages + } +} diff --git a/rpcserver.go b/rpcserver.go index 4cb970d..a6bd4e7 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -38,8 +38,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcjson/btcws" + "github.com/btcsuite/btcd/btcjson/v2/btcjson" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcrpcclient" @@ -52,32 +52,25 @@ import ( ) // Error types to simplify the reporting of specific categories of -// errors, and their btcjson.Error creation. +// errors, and their *btcjson.RPCError creation. type ( // DeserializationError describes a failed deserializaion due to bad - // user input. It cooresponds to btcjson.ErrDeserialization. + // user input. It cooresponds to btcjson.ErrRPCDeserialization. DeserializationError struct { error } // InvalidParameterError describes an invalid parameter passed by - // the user. It cooresponds to btcjson.ErrInvalidParameter. + // the user. It cooresponds to btcjson.ErrRPCInvalidParameter. InvalidParameterError struct { error } // ParseError describes a failed parse due to bad user input. It - // cooresponds to btcjson.ErrParse. + // cooresponds to btcjson.ErrRPCParse. ParseError struct { error } - - // InvalidAddressOrKeyError describes a parse, network mismatch, or - // missing address error when decoding or validating an address or - // key. It cooresponds to btcjson.ErrInvalidAddressOrKey. - InvalidAddressOrKeyError struct { - error - } ) // Errors variables that are defined once here to avoid duplication below. @@ -90,29 +83,34 @@ var ( errors.New("minconf must be positive"), } - ErrAddressNotInWallet = btcjson.Error{ - Code: btcjson.ErrWallet.Code, + ErrAddressNotInWallet = btcjson.RPCError{ + Code: btcjson.ErrRPCWallet, Message: "address not found in wallet", } - ErrNoAccountSupport = btcjson.Error{ - Code: btcjson.ErrWalletInvalidAccountName.Code, - Message: "btcwallet does not support non-default accounts", - } - - ErrAccountNameNotFound = btcjson.Error{ - Code: btcjson.ErrWalletInvalidAccountName.Code, + ErrAccountNameNotFound = btcjson.RPCError{ + Code: btcjson.ErrRPCWalletInvalidAccountName, Message: "account name not found", } - ErrUnloadedWallet = btcjson.Error{ - Code: btcjson.ErrWallet.Code, + ErrUnloadedWallet = btcjson.RPCError{ + Code: btcjson.ErrRPCWallet, Message: "Request requires a wallet but wallet has not loaded yet", } - ErrNeedsChainSvr = btcjson.Error{ - Code: btcjson.ErrWallet.Code, - Message: "Request requires chain connected chain server", + ErrWalletUnlockNeeded = btcjson.RPCError{ + Code: btcjson.ErrRPCWalletUnlockNeeded, + Message: "Enter the wallet passphrase with walletpassphrase first", + } + + ErrNotImportedAccount = btcjson.RPCError{ + Code: btcjson.ErrRPCWallet, + Message: "imported addresses must belong to the imported account", + } + + ErrNoTransactionInfo = btcjson.RPCError{ + Code: btcjson.ErrRPCNoTxInfo, + Message: "No information for transaction", } ) @@ -121,27 +119,6 @@ var ( // these replacements so they can be handled once after an RPC handler has // returned and before the error is marshaled. -// checkAccountName verifies that the passed account name is for the default -// account or '*' to represent all accounts. This is necessary to return -// errors to RPC clients for invalid account names, as account support is -// currently missing from btcwallet. -func checkAccountName(account string) error { - if account != "" && account != "*" { - return ErrNoAccountSupport - } - return nil -} - -// checkDefaultAccount verifies that the passed account name is the default -// account. This is necessary to return errors to RPC clients for invalid -// account names, as account support is currently missing from btcwallet. -func checkDefaultAccount(account string) error { - if account != "" { - return ErrNoAccountSupport - } - return nil -} - // confirmed checks whether a transaction at height txHeight has met minconf // confirmations for a blockchain at height curHeight. func confirmed(minconf, txHeight, curHeight int32) bool { @@ -637,32 +614,31 @@ func (s *rpcServer) HandlerClosure(method string) requestHandlerClosure { chainSvr := s.chainSvr if handler, ok := s.handlerLookup(method); ok { - return func(request []byte, raw *rawRequest) btcjson.Reply { - cmd, err := btcjson.ParseMarshaledCmd(request) + return func(req *btcjson.Request) (interface{}, *btcjson.RPCError) { + cmd, err := btcjson.UnmarshalCmd(req) if err != nil { - return makeResponse(raw.ID, nil, - btcjson.ErrInvalidRequest) + return nil, btcjson.ErrRPCInvalidRequest } - - result, err := handler(wallet, chainSvr, cmd) - return makeResponse(raw.ID, result, err) + res, err := handler(wallet, chainSvr, cmd) + if err != nil { + return nil, jsonError(err) + } + return res, nil } } - return func(request []byte, raw *rawRequest) btcjson.Reply { + return func(req *btcjson.Request) (interface{}, *btcjson.RPCError) { if chainSvr == nil { - err := btcjson.Error{ + return nil, &btcjson.RPCError{ Code: -1, Message: "Chain server is disconnected", } - return makeResponse(raw.ID, nil, err) } - - res, err := chainSvr.RawRequest(raw.Method, raw.Params) - - // The raw result will only marshal correctly if called with the - // MarshalJSON method, and that method requires a pointer receiver. - return makeResponse(raw.ID, &res, err) + res, err := chainSvr.RawRequest(req.Method, req.Params) + if err != nil { + return nil, jsonError(err) + } + return &res, nil } } @@ -715,20 +691,10 @@ func throttled(threshold int64, h http.Handler) http.Handler { }) } -type rawRequest struct { - // "jsonrpc" value isn't checked so we exclude it. - ID interface{} `json:"id"` - Method string `json:"method"` - Params []json.RawMessage `json:"params"` -} - -// String returns a sanitized string for the request which may be safely -// logged. It is intended to strip private keys, passphrases, and any other -// secrets from request parameters before they may be saved to a log file. -// -// This intentionally implements the fmt.Stringer interface to prevent -// accidental leaking of secrets. -func (r *rawRequest) String() string { +// sanitizeRequest returns a sanitized string for the request which may be +// safely logged. It is intended to strip private keys, passphrases, and any +// other secrets from request parameters before they may be saved to a log file. +func sanitizeRequest(r *btcjson.Request) string { // These are considered unsafe to log, so sanitize parameters. switch r.Method { case "encryptwallet", "importprivkey", "importwallet", @@ -757,12 +723,12 @@ func idPointer(id interface{}) (p *interface{}) { // invalidAuth checks whether a websocket request is a valid (parsable) // authenticate request and checks the supplied username and passphrase // against the server auth. -func (s *rpcServer) invalidAuth(request []byte) bool { - cmd, err := btcjson.ParseMarshaledCmd(request) +func (s *rpcServer) invalidAuth(req *btcjson.Request) bool { + cmd, err := btcjson.UnmarshalCmd(req) if err != nil { return false } - authCmd, ok := cmd.(*btcws.AuthenticateCmd) + authCmd, ok := cmd.(*btcjson.AuthenticateCmd) if !ok { return false } @@ -797,20 +763,21 @@ func (s *rpcServer) WebsocketClientRespond(wsc *websocketClient) { out: for { select { - case request, ok := <-wsc.allRequests: + case reqBytes, ok := <-wsc.allRequests: if !ok { // client disconnected break out } - var raw rawRequest - if err := json.Unmarshal(request, &raw); err != nil { + var req btcjson.Request + err := json.Unmarshal(reqBytes, &req) + if err != nil { if !wsc.authenticated { // Disconnect immediately. break out } - resp := makeResponse(raw.ID, nil, - btcjson.ErrInvalidRequest) + resp := makeResponse(req.ID, nil, + btcjson.ErrRPCInvalidRequest) mresp, err := json.Marshal(resp) // We expect the marshal to succeed. If it // doesn't, it indicates some non-marshalable @@ -825,13 +792,13 @@ out: continue } - if raw.Method == "authenticate" { - if wsc.authenticated || s.invalidAuth(request) { + if req.Method == "authenticate" { + if wsc.authenticated || s.invalidAuth(&req) { // Disconnect immediately. break out } wsc.authenticated = true - resp := makeResponse(raw.ID, nil, nil) + resp := makeResponse(req.ID, nil, nil) // Expected to never fail. mresp, err := json.Marshal(resp) if err != nil { @@ -849,10 +816,10 @@ out: break out } - switch raw.Method { + switch req.Method { case "stop": s.Stop() - resp := makeResponse(raw.ID, + resp := makeResponse(req.ID, "btcwallet stopping.", nil) mresp, err := json.Marshal(resp) // Expected to never fail. @@ -865,33 +832,19 @@ out: } default: - f := s.HandlerClosure(raw.Method) + req := req // Copy for the closure + f := s.HandlerClosure(req.Method) wsc.wg.Add(1) - go func(request []byte, raw *rawRequest) { - resp := f(request, raw) - mresp, err := json.Marshal(resp) + go func() { + resp, jsonErr := f(&req) + mresp, err := btcjson.MarshalResponse(req.ID, resp, jsonErr) if err != nil { - // Completely unexpected error, but have seen - // it happen regardless. Log the sanitized - // request and begin clean shutdown, panicing - // if shutdown takes too long. - log.Criticalf("Unexpected error marshaling "+ - "response for request '%s': %v", - raw, err) - wsc.wg.Done() - - s.Stop() - go func() { - time.Sleep(30 * time.Second) - panic("shutdown took too long") - }() - - return + log.Errorf("Unable to marshal response: %v", err) + } else { + _ = wsc.send(mresp) } - _ = wsc.send(mresp) - wsc.wg.Done() - }(request, &raw) + }() } case <-s.quit: @@ -998,17 +951,17 @@ func (s *rpcServer) PostClientRPC(w http.ResponseWriter, r *http.Request) { // If unfound, the request is sent to the chain server for further // processing. While checking the methods, disallow authenticate // requests, as they are invalid for HTTP POST clients. - var raw rawRequest - err = json.Unmarshal(rpcRequest, &raw) + var req btcjson.Request + err = json.Unmarshal(rpcRequest, &req) if err != nil { - resp := makeResponse(raw.ID, nil, btcjson.ErrInvalidRequest) - mresp, err := json.Marshal(resp) - // We expect the marshal to succeed. If it doesn't, it - // indicates some non-marshalable type in the response. + resp, err := btcjson.MarshalResponse(req.ID, nil, btcjson.ErrRPCInvalidRequest) if err != nil { - panic(err) + log.Errorf("Unable to marshal response: %v", err) + http.Error(w, "500 Internal Server Error", + http.StatusInternalServerError) + return } - _, err = w.Write(mresp) + _, err = w.Write(resp) if err != nil { log.Warnf("Cannot write invalid request request to "+ "client: %v", err) @@ -1018,26 +971,28 @@ func (s *rpcServer) PostClientRPC(w http.ResponseWriter, r *http.Request) { // Create the response and error from the request. Two special cases // are handled for the authenticate and stop request methods. - var resp btcjson.Reply - switch raw.Method { + var res interface{} + var jsonErr *btcjson.RPCError + switch req.Method { case "authenticate": // Drop it. return case "stop": s.Stop() - resp = makeResponse(raw.ID, "btcwallet stopping.", nil) + res = "btcwallet stopping" default: - resp = s.HandlerClosure(raw.Method)(rpcRequest, &raw) + res, jsonErr = s.HandlerClosure(req.Method)(&req) } // Marshal and send. - mresp, err := json.Marshal(resp) - // All responses originating from us must be marshalable. + mresp, err := btcjson.MarshalResponse(req.ID, res, jsonErr) if err != nil { - panic(err) + log.Errorf("Unable to marshal response: %v", err) + http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) + return } - // Send marshaled response to client. - if _, err := w.Write(mresp); err != nil { + _, err = w.Write(mresp) + if err != nil { log.Warnf("Unable to respond to client: %v", err) } } @@ -1047,7 +1002,7 @@ type ( wsClientNotification interface { // This returns a slice only because some of these types result // in multpile client notifications. - notificationCmds(w *wallet.Wallet) []btcjson.Cmd + notificationCmds(w *wallet.Wallet) []interface{} } blockConnected waddrmgr.BlockStamp @@ -1063,17 +1018,17 @@ type ( btcdConnected bool ) -func (b blockConnected) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { - n := btcws.NewBlockConnectedNtfn(b.Hash.String(), b.Height) - return []btcjson.Cmd{n} +func (b blockConnected) notificationCmds(w *wallet.Wallet) []interface{} { + n := btcjson.NewBlockConnectedNtfn(b.Hash.String(), b.Height) + return []interface{}{n} } -func (b blockDisconnected) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { - n := btcws.NewBlockDisconnectedNtfn(b.Hash.String(), b.Height) - return []btcjson.Cmd{n} +func (b blockDisconnected) notificationCmds(w *wallet.Wallet) []interface{} { + n := btcjson.NewBlockDisconnectedNtfn(b.Hash.String(), b.Height) + return []interface{}{n} } -func (t relevantTx) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { +func (t relevantTx) notificationCmds(w *wallet.Wallet) []interface{} { syncBlock := w.Manager.SyncedTo() var block *wtxmgr.Block @@ -1092,33 +1047,33 @@ func (t relevantTx) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { } ltr := wallet.ListTransactions(details, syncBlock.Height, activeNet.Params) - ntfns := make([]btcjson.Cmd, len(ltr)) + ntfns := make([]interface{}, len(ltr)) for i := range ntfns { - ntfns[i] = btcws.NewTxNtfn(ltr[i].Account, <r[i]) + ntfns[i] = btcjson.NewNewTxNtfn(ltr[i].Account, ltr[i]) } return ntfns } -func (l managerLocked) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { - n := btcws.NewWalletLockStateNtfn("", bool(l)) - return []btcjson.Cmd{n} +func (l managerLocked) notificationCmds(w *wallet.Wallet) []interface{} { + n := btcjson.NewWalletLockStateNtfn(bool(l)) + return []interface{}{n} } -func (b confirmedBalance) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { - n := btcws.NewAccountBalanceNtfn("", +func (b confirmedBalance) notificationCmds(w *wallet.Wallet) []interface{} { + n := btcjson.NewAccountBalanceNtfn("", btcutil.Amount(b).ToBTC(), true) - return []btcjson.Cmd{n} + return []interface{}{n} } -func (b unconfirmedBalance) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { - n := btcws.NewAccountBalanceNtfn("", +func (b unconfirmedBalance) notificationCmds(w *wallet.Wallet) []interface{} { + n := btcjson.NewAccountBalanceNtfn("", btcutil.Amount(b).ToBTC(), false) - return []btcjson.Cmd{n} + return []interface{}{n} } -func (b btcdConnected) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { - n := btcws.NewBtcdConnectedNtfn(bool(b)) - return []btcjson.Cmd{n} +func (b btcdConnected) notificationCmds(w *wallet.Wallet) []interface{} { + n := btcjson.NewBtcdConnectedNtfn(bool(b)) + return []interface{}{n} } func (s *rpcServer) notificationListener() { @@ -1279,7 +1234,7 @@ out: ns := nmsg.notificationCmds(s.wallet) for _, n := range ns { - mn, err := n.MarshalJSON() + mn, err := btcjson.MarshalCmd(nil, n) // All notifications are expected to be // marshalable. if err != nil { @@ -1301,89 +1256,105 @@ out: } // requestHandler is a handler function to handle an unmarshaled and parsed -// request into a marshalable response. If the error is a btcjson.Error +// request into a marshalable response. If the error is a *btcjson.RPCError // or any of the above special error classes, the server will respond with // the JSON-RPC appropiate error code. All other errors use the wallet -// catch-all error code, btcjson.ErrWallet.Code. -type requestHandler func(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) +// catch-all error code, btcjson.ErrRPCWallet. +type requestHandler func(*wallet.Wallet, *chain.Client, interface{}) (interface{}, error) -var rpcHandlers = map[string]requestHandler{ +var rpcHandlers = map[string]struct { + handler requestHandler + + // Function variables cannot be compared against anything but nil, so + // use a boolean to record whether help generation is necessary. This + // is used by the tests to ensure that help can be generated for every + // implemented method. + // + // A single map and this bool is here is used rather than several maps + // for the unimplemented handlers so every method has exactly one + // handler function. + noHelp bool +}{ // Reference implementation wallet methods (implemented) - "addmultisigaddress": AddMultiSigAddress, - "createmultisig": CreateMultiSig, - "dumpprivkey": DumpPrivKey, - "getaccount": GetAccount, - "getaccountaddress": GetAccountAddress, - "getaddressesbyaccount": GetAddressesByAccount, - "getbalance": GetBalance, - "getbestblockhash": GetBestBlockHash, - "getblockcount": GetBlockCount, - "getinfo": GetInfo, - "getnewaddress": GetNewAddress, - "getrawchangeaddress": GetRawChangeAddress, - "getreceivedbyaccount": GetReceivedByAccount, - "getreceivedbyaddress": GetReceivedByAddress, - "gettransaction": GetTransaction, - "importprivkey": ImportPrivKey, - "keypoolrefill": KeypoolRefill, - "listaccounts": ListAccounts, - "listlockunspent": ListLockUnspent, - "listreceivedbyaccount": ListReceivedByAccount, - "listreceivedbyaddress": ListReceivedByAddress, - "listsinceblock": ListSinceBlock, - "listtransactions": ListTransactions, - "listunspent": ListUnspent, - "lockunspent": LockUnspent, - "sendfrom": SendFrom, - "sendmany": SendMany, - "sendtoaddress": SendToAddress, - "settxfee": SetTxFee, - "signmessage": SignMessage, - "signrawtransaction": SignRawTransaction, - "validateaddress": ValidateAddress, - "verifymessage": VerifyMessage, - "walletlock": WalletLock, - "walletpassphrase": WalletPassphrase, - "walletpassphrasechange": WalletPassphraseChange, + "addmultisigaddress": {handler: AddMultiSigAddress}, + "createmultisig": {handler: CreateMultiSig}, + "dumpprivkey": {handler: DumpPrivKey}, + "getaccount": {handler: GetAccount}, + "getaccountaddress": {handler: GetAccountAddress}, + "getaddressesbyaccount": {handler: GetAddressesByAccount}, + "getbalance": {handler: GetBalance}, + "getbestblockhash": {handler: GetBestBlockHash}, + "getblockcount": {handler: GetBlockCount}, + "getinfo": {handler: GetInfo}, + "getnewaddress": {handler: GetNewAddress}, + "getrawchangeaddress": {handler: GetRawChangeAddress}, + "getreceivedbyaccount": {handler: GetReceivedByAccount}, + "getreceivedbyaddress": {handler: GetReceivedByAddress}, + "gettransaction": {handler: GetTransaction}, + "help": {handler: Help}, + "importprivkey": {handler: ImportPrivKey}, + "keypoolrefill": {handler: KeypoolRefill}, + "listaccounts": {handler: ListAccounts}, + "listlockunspent": {handler: ListLockUnspent}, + "listreceivedbyaccount": {handler: ListReceivedByAccount}, + "listreceivedbyaddress": {handler: ListReceivedByAddress}, + "listsinceblock": {handler: ListSinceBlock}, + "listtransactions": {handler: ListTransactions}, + "listunspent": {handler: ListUnspent}, + "lockunspent": {handler: LockUnspent}, + "sendfrom": {handler: SendFrom}, + "sendmany": {handler: SendMany}, + "sendtoaddress": {handler: SendToAddress}, + "settxfee": {handler: SetTxFee}, + "signmessage": {handler: SignMessage}, + "signrawtransaction": {handler: SignRawTransaction}, + "validateaddress": {handler: ValidateAddress}, + "verifymessage": {handler: VerifyMessage}, + "walletlock": {handler: WalletLock}, + "walletpassphrase": {handler: WalletPassphrase}, + "walletpassphrasechange": {handler: WalletPassphraseChange}, // Reference implementation methods (still unimplemented) - "backupwallet": Unimplemented, - "dumpwallet": Unimplemented, - "getwalletinfo": Unimplemented, - "importwallet": Unimplemented, - "listaddressgroupings": Unimplemented, + "backupwallet": {handler: Unimplemented, noHelp: true}, + "dumpwallet": {handler: Unimplemented, noHelp: true}, + "getwalletinfo": {handler: Unimplemented, noHelp: true}, + "importwallet": {handler: Unimplemented, noHelp: true}, + "listaddressgroupings": {handler: Unimplemented, noHelp: true}, // Reference methods which can't be implemented by btcwallet due to // design decision differences - "encryptwallet": Unsupported, - "move": Unsupported, - "setaccount": Unsupported, + "encryptwallet": {handler: Unsupported, noHelp: true}, + "move": {handler: Unsupported, noHelp: true}, + "setaccount": {handler: Unsupported, noHelp: true}, // Extensions to the reference client JSON-RPC API - "createnewaccount": CreateNewAccount, - "exportwatchingwallet": ExportWatchingWallet, - "getbestblock": GetBestBlock, + "createnewaccount": {handler: CreateNewAccount}, + "exportwatchingwallet": {handler: ExportWatchingWallet}, + "getbestblock": {handler: GetBestBlock}, // This was an extension but the reference implementation added it as // well, but with a different API (no account parameter). It's listed // here because it hasn't been update to use the reference // implemenation's API. - "getunconfirmedbalance": GetUnconfirmedBalance, - "listaddresstransactions": ListAddressTransactions, - "listalltransactions": ListAllTransactions, - "renameaccount": RenameAccount, - "walletislocked": WalletIsLocked, + "getunconfirmedbalance": {handler: GetUnconfirmedBalance}, + "listaddresstransactions": {handler: ListAddressTransactions}, + "listalltransactions": {handler: ListAllTransactions}, + "renameaccount": {handler: RenameAccount}, + "walletislocked": {handler: WalletIsLocked}, } // Unimplemented handles an unimplemented RPC request with the // appropiate error. -func Unimplemented(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) { - return nil, btcjson.ErrUnimplemented +func Unimplemented(*wallet.Wallet, *chain.Client, interface{}) (interface{}, error) { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCUnimplemented, + Message: "Method unimplemented", + } } // Unsupported handles a standard bitcoind RPC request which is // unsupported by btcwallet due to design differences. -func Unsupported(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) { - return nil, btcjson.Error{ +func Unsupported(*wallet.Wallet, *chain.Client, interface{}) (interface{}, error) { + return nil, &btcjson.RPCError{ Code: -1, Message: "Request unsupported by btcwallet", } @@ -1391,16 +1362,16 @@ func Unsupported(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error // UnloadedWallet is the handler func that is run when a wallet has not been // loaded yet when trying to execute a wallet RPC. -func UnloadedWallet(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) { - return nil, ErrUnloadedWallet +func UnloadedWallet(*wallet.Wallet, *chain.Client, interface{}) (interface{}, error) { + return nil, &ErrUnloadedWallet } // NoEncryptedWallet is the handler func that is run when no wallet has been // created by the user yet. // loaded yet when trying to execute a wallet RPC. -func NoEncryptedWallet(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) { - return nil, btcjson.Error{ - Code: btcjson.ErrWallet.Code, +func NoEncryptedWallet(*wallet.Wallet, *chain.Client, interface{}) (interface{}, error) { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCWallet, Message: "Request requires a wallet but no wallet has been " + "created -- use createencryptedwallet to recover", } @@ -1417,7 +1388,8 @@ func NoEncryptedWallet(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, // the http post and (if the request is from a websocket connection) websocket // handler maps. If a suitable handler could not be found, ok is false. func lookupAnyHandler(method string) (f requestHandler, ok bool) { - f, ok = rpcHandlers[method] + handlerData, ok := rpcHandlers[method] + f = handlerData.handler return } @@ -1448,51 +1420,60 @@ func missingWalletHandlerFunc(method string) (f requestHandler, ok bool) { // requestHandlerClosure is a closure over a requestHandler or passthrough // request with the RPC server's wallet and chain server variables as part // of the closure context. -type requestHandlerClosure func([]byte, *rawRequest) btcjson.Reply +type requestHandlerClosure func(*btcjson.Request) (interface{}, *btcjson.RPCError) // makeResponse makes the JSON-RPC response struct for the result and error // returned by a requestHandler. The returned response is not ready for // marshaling and sending off to a client, but must be -func makeResponse(id, result interface{}, err error) btcjson.Reply { +func makeResponse(id, result interface{}, err error) btcjson.Response { idPtr := idPointer(id) if err != nil { - return btcjson.Reply{ - Id: idPtr, + return btcjson.Response{ + ID: idPtr, Error: jsonError(err), } } - return btcjson.Reply{ - Id: idPtr, - Result: result, + resultBytes, err := json.Marshal(result) + if err != nil { + return btcjson.Response{ + ID: idPtr, + Error: &btcjson.RPCError{ + Code: btcjson.ErrRPCInternal.Code, + Message: "Unexpected error marshalling result", + }, + } + } + return btcjson.Response{ + ID: idPtr, + Result: json.RawMessage(resultBytes), } } // jsonError creates a JSON-RPC error from the Go error. -func jsonError(err error) *btcjson.Error { +func jsonError(err error) *btcjson.RPCError { if err == nil { return nil } - jsonErr := btcjson.Error{ - Message: err.Error(), - } + var code btcjson.RPCErrorCode switch e := err.(type) { - case btcjson.Error: + case btcjson.RPCError: return &e - case *btcjson.Error: + case *btcjson.RPCError: return e case DeserializationError: - jsonErr.Code = btcjson.ErrDeserialization.Code + code = btcjson.ErrRPCDeserialization case InvalidParameterError: - jsonErr.Code = btcjson.ErrInvalidParameter.Code + code = btcjson.ErrRPCInvalidParameter case ParseError: - jsonErr.Code = btcjson.ErrParse.Code - case InvalidAddressOrKeyError: - jsonErr.Code = btcjson.ErrInvalidAddressOrKey.Code + code = btcjson.ErrRPCParse.Code default: // All other errors get the wallet error code. - jsonErr.Code = btcjson.ErrWallet.Code + code = btcjson.ErrRPCWallet + } + return &btcjson.RPCError{ + Code: code, + Message: err.Error(), } - return &jsonErr } // makeMultiSigScript is a helper function to combine common logic for @@ -1507,7 +1488,7 @@ func makeMultiSigScript(w *wallet.Wallet, keys []string, nRequired int) ([]byte, // mixture of the two. for i, a := range keys { // try to parse as pubkey address - a, err := btcutil.DecodeAddress(a, activeNet.Params) + a, err := decodeAddress(a, activeNet.Params) if err != nil { return nil, err } @@ -1524,7 +1505,7 @@ func makeMultiSigScript(w *wallet.Wallet, keys []string, nRequired int) ([]byte, apkinfo := ainfo.(waddrmgr.ManagedPubKeyAddress) // This will be an addresspubkey - a, err := btcutil.DecodeAddress(apkinfo.ExportPubKey(), + a, err := decodeAddress(apkinfo.ExportPubKey(), activeNet.Params) if err != nil { return nil, err @@ -1542,12 +1523,12 @@ func makeMultiSigScript(w *wallet.Wallet, keys []string, nRequired int) ([]byte, // AddMultiSigAddress handles an addmultisigaddress request by adding a // multisig address to the given wallet. -func AddMultiSigAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func AddMultiSigAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.AddMultisigAddressCmd) - err := checkDefaultAccount(cmd.Account) - if err != nil { - return nil, err + // If an account is specified, ensure that is the imported account. + if cmd.Account != nil && *cmd.Account != waddrmgr.ImportedAddrAccountName { + return nil, &ErrNotImportedAccount } script, err := makeMultiSigScript(w, cmd.Keys, cmd.NRequired) @@ -1571,7 +1552,7 @@ func AddMultiSigAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.C // CreateMultiSig handles an createmultisig request by returning a // multisig address for the given inputs. -func CreateMultiSig(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func CreateMultiSig(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.CreateMultisigCmd) script, err := makeMultiSigScript(w, cmd.Keys, cmd.NRequired) @@ -1594,19 +1575,19 @@ func CreateMultiSig(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) // DumpPrivKey handles a dumpprivkey request with the private key // for a single address, or an appropiate error if the wallet // is locked. -func DumpPrivKey(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func DumpPrivKey(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.DumpPrivKeyCmd) - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) + addr, err := decodeAddress(cmd.Address, activeNet.Params) if err != nil { - return nil, btcjson.ErrInvalidAddressOrKey + return nil, err } key, err := w.DumpWIFPrivateKey(addr) if isManagerLockedError(err) { // Address was found, but the private key isn't // accessible. - return nil, btcjson.ErrWalletUnlockNeeded + return nil, &ErrWalletUnlockNeeded } return key, err } @@ -1614,35 +1595,26 @@ func DumpPrivKey(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (in // DumpWallet handles a dumpwallet request by returning all private // keys in a wallet, or an appropiate error if the wallet is locked. // TODO: finish this to match bitcoind by writing the dump to a file. -func DumpWallet(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func DumpWallet(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { keys, err := w.DumpPrivKeys() if isManagerLockedError(err) { - return nil, btcjson.ErrWalletUnlockNeeded + return nil, &ErrWalletUnlockNeeded } return keys, err } -// ExportWatchingWallet handles an exportwatchingwallet request by exporting -// the current account wallet as a watching wallet (with no private keys), and -// returning base64-encoding of serialized account files. -// -// TODO: remove Download from the command, this always assumes download now. -func ExportWatchingWallet(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { - cmd := icmd.(*btcws.ExportWatchingWalletCmd) - - err := checkAccountName(cmd.Account) - if err != nil { - return nil, err - } - +// ExportWatchingWallet handles an exportwatchingwallet request by exporting the +// current wallet as a watching wallet (with no private keys), and returning +// base64-encoding of serialized account files. +func ExportWatchingWallet(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { return w.ExportWatchingWallet(cfg.WalletPass) } // GetAddressesByAccount handles a getaddressesbyaccount request by returning // all addresses for an account, or an error if the requested account does // not exist. -func GetAddressesByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetAddressesByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetAddressesByAccountCmd) account, err := w.Manager.LookupAccount(cmd.Account) @@ -1666,20 +1638,21 @@ func GetAddressesByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso // GetBalance handles a getbalance request by returning the balance for an // account (wallet), or an error if the requested account does not // exist. -func GetBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetBalanceCmd) var balance btcutil.Amount - var account uint32 var err error - if cmd.Account == nil || *cmd.Account == "*" { - balance, err = w.CalculateBalance(int32(cmd.MinConf)) + accountName := *cmd.Account + if accountName == "*" { + balance, err = w.CalculateBalance(int32(*cmd.MinConf)) } else { - account, err = w.Manager.LookupAccount(*cmd.Account) + var account uint32 + account, err = w.Manager.LookupAccount(accountName) if err != nil { return nil, err } - balance, err = w.CalculateAccountBalance(account, int32(cmd.MinConf)) + balance, err = w.CalculateAccountBalance(account, int32(*cmd.MinConf)) } if err != nil { return nil, err @@ -1689,9 +1662,9 @@ func GetBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (int // GetBestBlock handles a getbestblock request by returning a JSON object // with the height and hash of the most recently processed block. -func GetBestBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetBestBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { blk := w.Manager.SyncedTo() - result := &btcws.GetBestBlockResult{ + result := &btcjson.GetBestBlockResult{ Hash: blk.Hash.String(), Height: blk.Height, } @@ -1700,14 +1673,14 @@ func GetBestBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (i // GetBestBlockHash handles a getbestblockhash request by returning the hash // of the most recently processed block. -func GetBestBlockHash(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetBestBlockHash(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { blk := w.Manager.SyncedTo() return blk.Hash.String(), nil } // GetBlockCount handles a getblockcount request by returning the chain height // of the most recently processed block. -func GetBlockCount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetBlockCount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { blk := w.Manager.SyncedTo() return blk.Height, nil } @@ -1715,7 +1688,7 @@ func GetBlockCount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) ( // GetInfo handles a getinfo request by returning the a structure containing // information about the current state of btcwallet. // exist. -func GetInfo(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetInfo(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { // Call down to btcd for all of the information in this command known // by them. info, err := chainSvr.GetInfo() @@ -1732,9 +1705,6 @@ func GetInfo(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interf // to using the manager version. info.WalletVersion = int32(waddrmgr.LatestMgrVersion) info.Balance = bal.ToBTC() - // Keypool times are not tracked. set to current time. - info.KeypoolOldest = time.Now().Unix() - info.KeypoolSize = int32(cfg.KeypoolSize) info.PaytxFee = w.FeeIncrement.ToBTC() // We don't set the following since they don't make much sense in the // wallet architecture: @@ -1744,26 +1714,45 @@ func GetInfo(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interf return info, nil } +func decodeAddress(s string, params *chaincfg.Params) (btcutil.Address, error) { + addr, err := btcutil.DecodeAddress(s, params) + if err != nil { + msg := fmt.Sprintf("Invalid address %q: decode failed with %#q", s, err) + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidAddressOrKey, + Message: msg, + } + } + if !addr.IsForNet(activeNet.Params) { + msg := fmt.Sprintf("Invalid address %q: not intended for use on %s", + addr, params.Name) + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidAddressOrKey, + Message: msg, + } + } + return addr, nil +} + // GetAccount handles a getaccount request by returning the account name // associated with a single address. -func GetAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetAccountCmd) - // Is address valid? - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) - if err != nil || !addr.IsForNet(activeNet.Params) { - return nil, btcjson.ErrInvalidAddressOrKey + addr, err := decodeAddress(cmd.Address, activeNet.Params) + if err != nil { + return nil, err } // Fetch the associated account account, err := w.Manager.AddrAccount(addr) if err != nil { - return nil, ErrAddressNotInWallet + return nil, &ErrAddressNotInWallet } acctName, err := w.Manager.AccountName(account) if err != nil { - return nil, ErrAccountNameNotFound + return nil, &ErrAccountNameNotFound } return acctName, nil } @@ -1773,8 +1762,8 @@ func GetAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (int // appear in the blockchain, or any tx that has arrived in the btcd mempool). // If the most recently-requested address has been used, a new address (the // next chained address in the keypool) is used. This can fail if the keypool -// runs out (and will return btcjson.ErrWalletKeypoolRanOut if that happens). -func GetAccountAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +// runs out (and will return btcjson.ErrRPCWalletKeypoolRanOut if that happens). +func GetAccountAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetAccountAddressCmd) account, err := w.Manager.LookupAccount(cmd.Account) @@ -1791,10 +1780,10 @@ func GetAccountAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cm // GetUnconfirmedBalance handles a getunconfirmedbalance extension request // by returning the current unconfirmed balance of an account. -func GetUnconfirmedBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { - cmd := icmd.(*btcws.GetUnconfirmedBalanceCmd) +func GetUnconfirmedBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { + cmd := icmd.(*btcjson.GetUnconfirmedBalanceCmd) - account, err := w.Manager.LookupAccount(cmd.Account) + account, err := w.Manager.LookupAccount(*cmd.Account) if err != nil { return nil, err } @@ -1813,28 +1802,38 @@ func GetUnconfirmedBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso // ImportPrivKey handles an importprivkey request by parsing // a WIF-encoded private key and adding it to an account. -func ImportPrivKey(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func ImportPrivKey(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.ImportPrivKeyCmd) - // Yes, Label is the account name... - err := checkDefaultAccount(cmd.Label) - if err != nil { - return nil, err + // Ensure that private keys are only imported to the correct account. + // + // Yes, Label is the account name. + if *cmd.Label != waddrmgr.ImportedAddrAccountName { + return nil, &ErrNotImportedAccount } wif, err := btcutil.DecodeWIF(cmd.PrivKey) - if err != nil || !wif.IsForNet(activeNet.Params) { - return nil, btcjson.ErrInvalidAddressOrKey + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidAddressOrKey, + Message: "WIF decode failed: " + err.Error(), + } + } + if !wif.IsForNet(activeNet.Params) { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidAddressOrKey, + Message: "Key is not intended for " + activeNet.Params.Name, + } } // Import the private key, handling any errors. - _, err = w.ImportPrivateKey(wif, nil, cmd.Rescan) + _, err = w.ImportPrivateKey(wif, nil, *cmd.Rescan) switch { case isManagerDuplicateAddressError(err): // Do not return duplicate key errors to the client. return nil, nil case isManagerLockedError(err): - return nil, btcjson.ErrWalletUnlockNeeded + return nil, &ErrWalletUnlockNeeded } return nil, err @@ -1842,15 +1841,15 @@ func ImportPrivKey(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) ( // KeypoolRefill handles the keypoolrefill command. Since we handle the keypool // automatically this does nothing since refilling is never manually required. -func KeypoolRefill(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func KeypoolRefill(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { return nil, nil } // CreateNewAccount handles a createnewaccount request by creating and // returning a new account. If the last account has no transaction history // as per BIP 0044 a new account cannot be created so an error will be returned. -func CreateNewAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { - cmd := icmd.(*btcws.CreateNewAccountCmd) +func CreateNewAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { + cmd := icmd.(*btcjson.CreateNewAccountCmd) // Check that we are within the maximum allowed non-empty accounts limit. account, err := w.Manager.LastAccount() @@ -1870,8 +1869,8 @@ func CreateNewAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd _, err = w.Manager.NewAccount(cmd.Account) if isManagerLockedError(err) { - return nil, btcjson.Error{ - Code: btcjson.ErrWalletUnlockNeeded.Code, + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCWalletUnlockNeeded, Message: "Creating an account requires the wallet to be unlocked. " + "Enter the wallet passphrase with walletpassphrase to unlock", } @@ -1881,8 +1880,8 @@ func CreateNewAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd // RenameAccount handles a renameaccount request by renaming an account. // If the account does not exist an appropiate error will be returned. -func RenameAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { - cmd := icmd.(*btcws.RenameAccountCmd) +func RenameAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { + cmd := icmd.(*btcjson.RenameAccountCmd) // Check that given account exists account, err := w.Manager.LookupAccount(cmd.OldAccount) if err != nil { @@ -1896,10 +1895,10 @@ func RenameAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) ( // error is returned. // TODO: Follow BIP 0044 and warn if number of unused addresses exceeds // the gap limit. -func GetNewAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetNewAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetNewAddressCmd) - account, err := w.Manager.LookupAccount(cmd.Account) + account, err := w.Manager.LookupAccount(*cmd.Account) if err != nil { return nil, err } @@ -1918,9 +1917,9 @@ func GetNewAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) ( // // Note: bitcoind allows specifying the account as an optional parameter, // but ignores the parameter. -func GetRawChangeAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetRawChangeAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetRawChangeAddressCmd) - account, err := w.Manager.LookupAccount(cmd.Account) + account, err := w.Manager.LookupAccount(*cmd.Account) if err != nil { return nil, err } @@ -1935,7 +1934,7 @@ func GetRawChangeAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson. // GetReceivedByAccount handles a getreceivedbyaccount request by returning // the total amount received by addresses of an account. -func GetReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetReceivedByAccountCmd) account, err := w.Manager.LookupAccount(cmd.Account) @@ -1943,7 +1942,7 @@ func GetReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson return nil, err } - bal, _, err := w.TotalReceivedForAccount(account, int32(cmd.MinConf)) + bal, _, err := w.TotalReceivedForAccount(account, int32(*cmd.MinConf)) if err != nil { return nil, err } @@ -1953,14 +1952,14 @@ func GetReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson // GetReceivedByAddress handles a getreceivedbyaddress request by returning // the total amount received by a single address. -func GetReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetReceivedByAddressCmd) - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) + addr, err := decodeAddress(cmd.Address, activeNet.Params) if err != nil { - return nil, InvalidAddressOrKeyError{err} + return nil, err } - total, err := w.TotalReceivedForAddr(addr, int32(cmd.MinConf)) + total, err := w.TotalReceivedForAddr(addr, int32(*cmd.MinConf)) if err != nil { return nil, err } @@ -1970,12 +1969,15 @@ func GetReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson // GetTransaction handles a gettransaction request by returning details about // a single transaction saved by wallet. -func GetTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func GetTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetTransactionCmd) txSha, err := wire.NewShaHashFromStr(cmd.Txid) if err != nil { - return nil, btcjson.ErrDecodeHexString + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCDecodeHexString, + Message: "Transaction hash string decode failed: " + err.Error(), + } } details, err := w.TxStore.TxDetails(txSha) @@ -1983,7 +1985,7 @@ func GetTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) return nil, err } if details == nil { - return nil, btcjson.ErrNoTxInfo + return nil, &ErrNoTransactionInfo } syncBlock := w.Manager.SyncedTo() @@ -2046,7 +2048,6 @@ func GetTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) ret.Details = make([]btcjson.GetTransactionDetailsResult, 1, len(details.Credits)+1) ret.Details[0] = btcjson.GetTransactionDetailsResult{ - Account: waddrmgr.DefaultAccountName, Category: "send", Amount: (-debitTotal).ToBTC(), // negative since it is a send Fee: feeF64, @@ -2069,7 +2070,6 @@ func GetTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) } ret.Details = append(ret.Details, btcjson.GetTransactionDetailsResult{ - Account: waddrmgr.DefaultAccountName, Category: credCat, Amount: cred.Amount.ToBTC(), Address: addr, @@ -2080,9 +2080,129 @@ func GetTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) return ret, nil } +// These generators create the following global variables in this package: +// +// var localeHelpDescs map[string]func() map[string]string +// var requestUsages string +// +// localeHelpDescs maps from locale strings (e.g. "en_US") to a function that +// builds a map of help texts for each RPC server method. This prevents help +// text maps for every locale map from being rooted and created during init. +// Instead, the appropiate function is looked up when help text is first needed +// using the current locale and saved to the global below for futher reuse. +// +// requestUsages contains single line usages for every supported request, +// separated by newlines. It is set during init. These usages are used for all +// locales. +// +//go:generate go run internal/rpchelp/genrpcserverhelp.go -tags generate +//go:generate gofmt -w rpcserverhelp.go + +var helpDescs map[string]string +var helpDescsMu sync.Mutex // Help may execute concurrently, so synchronize access. + +// Help handles the help request by returning one line usage of all available +// methods, or full help for a specific method. +func Help(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { + cmd := icmd.(*btcjson.HelpCmd) + + // btcd returns different help messages depending on the kind of + // connection the client is using. Only methods availble to HTTP POST + // clients are available to be used by wallet clients, even though + // wallet itself is a websocket client to btcd. Therefore, create a + // POST client as needed. + // + // Returns nil if chainSvr is currently nil or there is an error + // creating the client. + // + // This is hacky and is probably better handled by exposing help usage + // texts in a non-internal btcd package. + postClient := func() *btcrpcclient.Client { + if chainSvr == nil { + return nil + } + var certs []byte + if !cfg.DisableClientTLS { + var err error + certs, err = ioutil.ReadFile(cfg.CAFile) + if err != nil { + return nil + } + } + conf := btcrpcclient.ConnConfig{ + Host: cfg.RPCConnect, + User: cfg.BtcdUsername, + Pass: cfg.BtcdPassword, + DisableTLS: cfg.DisableClientTLS, + Certificates: certs, + HTTPPostMode: true, + } + client, err := btcrpcclient.New(&conf, nil) + if err != nil { + return nil + } + return client + } + + if cmd.Command == nil || *cmd.Command == "" { + // Prepend chain server usage if it is available. + usages := requestUsages + client := postClient() + if client != nil { + rawChainUsage, err := client.RawRequest("help", nil) + var chainUsage string + if err == nil { + _ = json.Unmarshal([]byte(rawChainUsage), &chainUsage) + } + if chainUsage != "" { + usages = "Chain server usage:\n\n" + chainUsage + "\n\n" + + "Wallet server usage (overrides chain requests):\n\n" + + requestUsages + } + } + return usages, nil + } + + defer helpDescsMu.Unlock() + helpDescsMu.Lock() + + if helpDescs == nil { + // TODO: Allow other locales to be set via config or detemine + // this from environment variables. For now, hardcode US + // English. + helpDescs = localeHelpDescs["en_US"]() + } + + helpText, ok := helpDescs[*cmd.Command] + if ok { + return helpText, nil + } + + // Return the chain server's detailed help if possible. + var chainHelp string + client := postClient() + if client != nil { + param := make([]byte, len(*cmd.Command)+2) + param[0] = '"' + copy(param[1:], *cmd.Command) + param[len(param)-1] = '"' + rawChainHelp, err := client.RawRequest("help", []json.RawMessage{param}) + if err == nil { + _ = json.Unmarshal([]byte(rawChainHelp), &chainHelp) + } + } + if chainHelp != "" { + return chainHelp, nil + } + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidParameter, + Message: fmt.Sprintf("No help for method '%s'", *cmd.Command), + } +} + // ListAccounts handles a listaccounts request by returning a map of account // names to their balances. -func ListAccounts(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func ListAccounts(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.ListAccountsCmd) accountBalances := map[string]float64{} @@ -2090,12 +2210,13 @@ func ListAccounts(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (i if err != nil { return nil, err } + minConf := int32(*cmd.MinConf) for _, account := range accounts { acctName, err := w.Manager.AccountName(account) if err != nil { - return nil, ErrAccountNameNotFound + return nil, &ErrAccountNameNotFound } - bal, err := w.CalculateAccountBalance(account, int32(cmd.MinConf)) + bal, err := w.CalculateAccountBalance(account, minConf) if err != nil { return nil, err } @@ -2107,7 +2228,7 @@ func ListAccounts(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (i // ListLockUnspent handles a listlockunspent request by returning an slice of // all locked outpoints. -func ListLockUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func ListLockUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { return w.LockedOutpoints(), nil } @@ -2121,7 +2242,7 @@ func ListLockUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) // default: one; // "includeempty": whether or not to include addresses that have no transactions - // default: false. -func ListReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func ListReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.ListReceivedByAccountCmd) accounts, err := w.Manager.AllAccounts() @@ -2130,13 +2251,14 @@ func ListReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso } ret := make([]btcjson.ListReceivedByAccountResult, 0, len(accounts)) + minConf := int32(*cmd.MinConf) for _, account := range accounts { acctName, err := w.Manager.AccountName(account) if err != nil { - return nil, ErrAccountNameNotFound + return nil, &ErrAccountNameNotFound } bal, confirmations, err := w.TotalReceivedForAccount(account, - int32(cmd.MinConf)) + minConf) if err != nil { return nil, err } @@ -2160,7 +2282,7 @@ func ListReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso // default: one; // "includeempty": whether or not to include addresses that have no transactions - // default: false. -func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.ListReceivedByAddressCmd) // Intermediate data for each address. @@ -2179,26 +2301,25 @@ func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso // Intermediate data for all addresses. allAddrData := make(map[string]AddrData) - if cmd.IncludeEmpty { - // Create an AddrData entry for each active address in the account. - // Otherwise we'll just get addresses from transactions later. - sortedAddrs, err := w.SortedActivePaymentAddresses() - if err != nil { - return nil, err - } - for _, address := range sortedAddrs { - // There might be duplicates, just overwrite them. - allAddrData[address] = AddrData{} - } + // Create an AddrData entry for each active address in the account. + // Otherwise we'll just get addresses from transactions later. + sortedAddrs, err := w.SortedActivePaymentAddresses() + if err != nil { + return nil, err + } + for _, address := range sortedAddrs { + // There might be duplicates, just overwrite them. + allAddrData[address] = AddrData{} } + minConf := *cmd.MinConf var endHeight int32 - if cmd.MinConf == -1 { + if minConf == 0 { endHeight = -1 } else { - endHeight = syncBlock.Height - int32(cmd.MinConf) + 1 + endHeight = syncBlock.Height - int32(minConf) + 1 } - err := w.TxStore.RangeTransactions(0, endHeight, func(details []wtxmgr.TxDetails) (bool, error) { + err = w.TxStore.RangeTransactions(0, endHeight, func(details []wtxmgr.TxDetails) (bool, error) { confirmations := confirms(details[0].Block.Height, syncBlock.Height) for _, tx := range details { for _, cred := range tx.Credits { @@ -2239,7 +2360,6 @@ func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso idx := 0 for address, addrData := range allAddrData { ret[idx] = btcjson.ListReceivedByAddressResult{ - Account: waddrmgr.DefaultAccountName, Address: address, Amount: addrData.amount.ToBTC(), Confirmations: uint64(addrData.confirmations), @@ -2252,29 +2372,30 @@ func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso // ListSinceBlock handles a listsinceblock request by returning an array of maps // with details of sent and received wallet transactions since the given block. -func ListSinceBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func ListSinceBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.ListSinceBlockCmd) syncBlock := w.Manager.SyncedTo() + targetConf := int64(*cmd.TargetConfirmations) // For the result we need the block hash for the last block counted // in the blockchain due to confirmations. We send this off now so that // it can arrive asynchronously while we figure out the rest. - gbh := chainSvr.GetBlockHashAsync(int64(syncBlock.Height) + 1 - int64(cmd.TargetConfirmations)) + gbh := chainSvr.GetBlockHashAsync(int64(syncBlock.Height) + 1 - targetConf) var start int32 - if cmd.BlockHash != "" { - hash, err := wire.NewShaHashFromStr(cmd.BlockHash) + if cmd.BlockHash != nil { + hash, err := wire.NewShaHashFromStr(*cmd.BlockHash) if err != nil { return nil, DeserializationError{err} } - block, err := chainSvr.GetBlock(hash) + block, err := chainSvr.GetBlockVerbose(hash, false) if err != nil { return nil, err } - start = int32(block.Height()) + 1 + start = int32(block.Height) + 1 } - end := syncBlock.Height - int32(cmd.TargetConfirmations) + 1 + end := syncBlock.Height - int32(targetConf) + 1 txInfoList, err := w.ListSinceBlock(start, end, syncBlock.Height) if err != nil { @@ -2296,17 +2417,24 @@ func ListSinceBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) // ListTransactions handles a listtransactions request by returning an // array of maps with details of sent and recevied wallet transactions. -func ListTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func ListTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.ListTransactionsCmd) + // TODO: ListTransactions does not currently understand the difference + // between transactions pertaining to one account from another. This + // will be resolved when wtxmgr is combined with the waddrmgr namespace. + if cmd.Account != nil { - err := checkAccountName(*cmd.Account) - if err != nil { - return nil, err + // For now, don't bother trying to continue if the user + // specified an account, since this can't be (easily or + // efficiently) calculated. + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCWallet, + Message: "Transactions are not yet grouped by account", } } - return w.ListTransactions(cmd.From, cmd.Count) + return w.ListTransactions(*cmd.From, *cmd.Count) } // ListAddressTransactions handles a listaddresstransactions request by @@ -2314,74 +2442,52 @@ func ListTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd // transactions. The form of the reply is identical to listtransactions, // but the array elements are limited to transaction details which are // about the addresess included in the request. -func ListAddressTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { - cmd := icmd.(*btcws.ListAddressTransactionsCmd) - - err := checkAccountName(cmd.Account) - if err != nil { - return nil, err - } +func ListAddressTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { + cmd := icmd.(*btcjson.ListAddressTransactionsCmd) // Decode addresses. - pkHashMap := make(map[string]struct{}) + hash160Map := make(map[string]struct{}) for _, addrStr := range cmd.Addresses { - addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params) + addr, err := decodeAddress(addrStr, activeNet.Params) if err != nil { - return nil, btcjson.ErrInvalidAddressOrKey + return nil, err } - apkh, ok := addr.(*btcutil.AddressPubKeyHash) - if !ok || !apkh.IsForNet(activeNet.Params) { - return nil, btcjson.ErrInvalidAddressOrKey - } - pkHashMap[string(addr.ScriptAddress())] = struct{}{} + hash160Map[string(addr.ScriptAddress())] = struct{}{} } - return w.ListAddressTransactions(pkHashMap) + return w.ListAddressTransactions(hash160Map) } // ListAllTransactions handles a listalltransactions request by returning // a map with details of sent and recevied wallet transactions. This is // similar to ListTransactions, except it takes only a single optional // argument for the account name and replies with all transactions. -func ListAllTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { - cmd := icmd.(*btcws.ListAllTransactionsCmd) - - if cmd.Account != nil { - err := checkAccountName(*cmd.Account) - if err != nil { - return nil, err - } - } - +func ListAllTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { return w.ListAllTransactions() } // ListUnspent handles the listunspent command. -func ListUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func ListUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.ListUnspentCmd) - addresses := make(map[string]bool) - if len(cmd.Addresses) != 0 { + var addresses map[string]struct{} + if cmd.Addresses != nil { + addresses = make(map[string]struct{}) // confirm that all of them are good: - for _, as := range cmd.Addresses { - a, err := btcutil.DecodeAddress(as, activeNet.Params) + for _, as := range *cmd.Addresses { + a, err := decodeAddress(as, activeNet.Params) if err != nil { - return nil, btcjson.ErrInvalidAddressOrKey + return nil, err } - - if _, ok := addresses[a.EncodeAddress()]; ok { - // duplicate - return nil, btcjson.ErrInvalidParameter - } - addresses[a.EncodeAddress()] = true + addresses[a.EncodeAddress()] = struct{}{} } } - return w.ListUnspent(int32(cmd.MinConf), int32(cmd.MaxConf), addresses) + return w.ListUnspent(int32(*cmd.MinConf), int32(*cmd.MaxConf), addresses) } // LockUnspent handles the lockunspent command. -func LockUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func LockUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.LockUnspentCmd) switch { @@ -2407,7 +2513,7 @@ func LockUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (in // sendPairs is a helper routine to reduce duplicated code when creating and // sending payment transactions. It returns the transaction hash in string // format upon success. -func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, +func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd interface{}, amounts map[string]btcutil.Amount, account uint32, minconf int32) (string, error) { // Create transaction, replying with an error if the creation @@ -2418,7 +2524,7 @@ func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, case err == wallet.ErrNonPositiveAmount: return "", ErrNeedPositiveAmount case isManagerLockedError(err): - return "", btcjson.ErrWalletUnlockNeeded + return "", &ErrWalletUnlockNeeded } return "", err @@ -2428,12 +2534,12 @@ func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, rec, err := wtxmgr.NewTxRecordFromMsgTx(createdTx.MsgTx, time.Now()) if err != nil { log.Errorf("Cannot create record for created transaction: %v", err) - return "", btcjson.ErrInternal + return "", btcjson.ErrRPCInternal } err = w.TxStore.InsertTx(rec, nil) if err != nil { log.Errorf("Error adding sent tx history: %v", err) - return "", btcjson.ErrInternal + return "", btcjson.ErrRPCInternal } if createdTx.ChangeIndex >= 0 { @@ -2441,7 +2547,7 @@ func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, if err != nil { log.Errorf("Error adding change address for sent "+ "tx: %v", err) - return "", btcjson.ErrInternal + return "", btcjson.ErrRPCInternal } } @@ -2461,9 +2567,18 @@ func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, // address. Leftover inputs not sent to the payment address or a fee for // the miner are sent back to a new address in the wallet. Upon success, // the TxID for the created transaction is returned. -func SendFrom(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func SendFrom(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.SendFromCmd) + // Transaction comments are not yet supported. Error instead of + // pretending to save them. + if cmd.Comment != nil || cmd.CommentTo != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCUnimplemented, + Message: "Transaction comments are not yet supported", + } + } + account, err := w.Manager.LookupAccount(cmd.FromAccount) if err != nil { return nil, err @@ -2473,15 +2588,20 @@ func SendFrom(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter if cmd.Amount < 0 { return nil, ErrNeedPositiveAmount } - if cmd.MinConf < 0 { + minConf := int32(*cmd.MinConf) + if minConf < 0 { return nil, ErrNeedPositiveMinconf } // Create map of address and amount pairs. + amt, err := btcutil.NewAmount(cmd.Amount) + if err != nil { + return nil, err + } pairs := map[string]btcutil.Amount{ - cmd.ToAddress: btcutil.Amount(cmd.Amount), + cmd.ToAddress: amt, } - return sendPairs(w, chainSvr, cmd, pairs, account, int32(cmd.MinConf)) + return sendPairs(w, chainSvr, cmd, pairs, account, minConf) } // SendMany handles a sendmany RPC request by creating a new transaction @@ -2489,26 +2609,40 @@ func SendFrom(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter // payment addresses. Leftover inputs not sent to the payment address // or a fee for the miner are sent back to a new address in the wallet. // Upon success, the TxID for the created transaction is returned. -func SendMany(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func SendMany(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.SendManyCmd) + // Transaction comments are not yet supported. Error instead of + // pretending to save them. + if cmd.Comment != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCUnimplemented, + Message: "Transaction comments are not yet supported", + } + } + account, err := w.Manager.LookupAccount(cmd.FromAccount) if err != nil { return nil, err } // Check that minconf is positive. - if cmd.MinConf < 0 { + minConf := int32(*cmd.MinConf) + if minConf < 0 { return nil, ErrNeedPositiveMinconf } // Recreate address/amount pairs, using btcutil.Amount. pairs := make(map[string]btcutil.Amount, len(cmd.Amounts)) for k, v := range cmd.Amounts { - pairs[k] = btcutil.Amount(v) + amt, err := btcutil.NewAmount(v) + if err != nil { + return nil, err + } + pairs[k] = amt } - return sendPairs(w, chainSvr, cmd, pairs, account, int32(cmd.MinConf)) + return sendPairs(w, chainSvr, cmd, pairs, account, minConf) } // SendToAddress handles a sendtoaddress RPC request by creating a new @@ -2516,17 +2650,31 @@ func SendMany(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter // payment address. Leftover inputs not sent to the payment address or a fee // for the miner are sent back to a new address in the wallet. Upon success, // the TxID for the created transaction is returned. -func SendToAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func SendToAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.SendToAddressCmd) + // Transaction comments are not yet supported. Error instead of + // pretending to save them. + if cmd.Comment != nil || cmd.CommentTo != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCUnimplemented, + Message: "Transaction comments are not yet supported", + } + } + + amt, err := btcutil.NewAmount(cmd.Amount) + if err != nil { + return nil, err + } + // Check that signed integer parameters are positive. - if cmd.Amount < 0 { + if amt < 0 { return nil, ErrNeedPositiveAmount } // Mock up map of address and amount pairs. pairs := map[string]btcutil.Amount{ - cmd.Address: btcutil.Amount(cmd.Amount), + cmd.Address: amt, } // sendtoaddress always spends from the default account, this matches bitcoind @@ -2534,7 +2682,7 @@ func SendToAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) ( } // SetTxFee sets the transaction fee per kilobyte added to transactions. -func SetTxFee(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func SetTxFee(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.SetTxFeeCmd) // Check that amount is not negative. @@ -2542,7 +2690,11 @@ func SetTxFee(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter return nil, ErrNeedPositiveAmount } - w.FeeIncrement = btcutil.Amount(cmd.Amount) + incr, err := btcutil.NewAmount(cmd.Amount) + if err != nil { + return nil, err + } + w.FeeIncrement = incr // A boolean true result is returned upon success. return true, nil @@ -2550,20 +2702,26 @@ func SetTxFee(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter // SignMessage signs the given message with the private key for the given // address -func SignMessage(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func SignMessage(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.SignMessageCmd) - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) + addr, err := decodeAddress(cmd.Address, activeNet.Params) if err != nil { - return nil, ParseError{err} + return nil, err } ainfo, err := w.Manager.Address(addr) if err != nil { - return nil, btcjson.ErrInvalidAddressOrKey + return nil, err + } + pka, ok := ainfo.(waddrmgr.ManagedPubKeyAddress) + if !ok { + msg := fmt.Sprintf("Address '%s' does not have an associated private key", addr) + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidAddressOrKey, + Message: msg, + } } - - pka := ainfo.(waddrmgr.ManagedPubKeyAddress) privKey, err := pka.PrivKey() if err != nil { return nil, err @@ -2587,12 +2745,12 @@ type pendingTx struct { } // SignRawTransaction handles the signrawtransaction command. -func SignRawTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func SignRawTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.SignRawTransactionCmd) serializedTx, err := decodeHexStr(cmd.RawTx) if err != nil { - return nil, btcjson.ErrDecodeHexString + return nil, err } msgTx := wire.NewMsgTx() err = msgTx.Deserialize(bytes.NewBuffer(serializedTx)) @@ -2606,7 +2764,11 @@ func SignRawTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.C // to make sure that they match the blockchain if present. inputs := make(map[wire.OutPoint][]byte) scripts := make(map[string][]byte) - for _, rti := range cmd.Inputs { + var cmdInputs []btcjson.RawTxInput + if cmd.Inputs != nil { + cmdInputs = *cmd.Inputs + } + for _, rti := range cmdInputs { inputSha, err := wire.NewShaHashFromStr(rti.Txid) if err != nil { return nil, DeserializationError{err} @@ -2614,7 +2776,7 @@ func SignRawTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.C script, err := decodeHexStr(rti.ScriptPubKey) if err != nil { - return nil, DeserializationError{err} + return nil, err } // redeemScript is only actually used iff the user provided @@ -2623,10 +2785,10 @@ func SignRawTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.C // get scripts from the wallet. // Empty strings are ok for this one and hex.DecodeString will // DTRT. - if len(cmd.PrivKeys) != 0 { + if cmd.PrivKeys != nil && len(*cmd.PrivKeys) != 0 { redeemScript, err := decodeHexStr(rti.RedeemScript) if err != nil { - return nil, DeserializationError{err} + return nil, err } addr, err := btcutil.NewAddressScriptHash(redeemScript, @@ -2676,10 +2838,10 @@ func SignRawTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.C // they are the keys that we may use for signing. If empty we will // use any keys known to us already. var keys map[string]*btcutil.WIF - if len(cmd.PrivKeys) != 0 { + if cmd.PrivKeys != nil { keys = make(map[string]*btcutil.WIF) - for _, key := range cmd.PrivKeys { + for _, key := range *cmd.PrivKeys { wif, err := btcutil.DecodeWIF(key) if err != nil { return nil, DeserializationError{err} @@ -2699,28 +2861,23 @@ func SignRawTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.C } } - hashType := txscript.SigHashAll - if cmd.Flags != "" { - switch cmd.Flags { - case "ALL": - hashType = txscript.SigHashAll - case "NONE": - hashType = txscript.SigHashNone - case "SINGLE": - hashType = txscript.SigHashSingle - case "ALL|ANYONECANPAY": - hashType = txscript.SigHashAll | - txscript.SigHashAnyOneCanPay - case "NONE|ANYONECANPAY": - hashType = txscript.SigHashNone | - txscript.SigHashAnyOneCanPay - case "SINGLE|ANYONECANPAY": - hashType = txscript.SigHashSingle | - txscript.SigHashAnyOneCanPay - default: - e := errors.New("Invalid sighash parameter") - return nil, InvalidParameterError{e} - } + var hashType txscript.SigHashType + switch *cmd.Flags { + case "ALL": + hashType = txscript.SigHashAll + case "NONE": + hashType = txscript.SigHashNone + case "SINGLE": + hashType = txscript.SigHashSingle + case "ALL|ANYONECANPAY": + hashType = txscript.SigHashAll | txscript.SigHashAnyOneCanPay + case "NONE|ANYONECANPAY": + hashType = txscript.SigHashNone | txscript.SigHashAnyOneCanPay + case "SINGLE|ANYONECANPAY": + hashType = txscript.SigHashSingle | txscript.SigHashAnyOneCanPay + default: + e := errors.New("Invalid sighash parameter") + return nil, InvalidParameterError{e} } // We have checked the rest of the args. now we can collect the async @@ -2861,11 +3018,11 @@ func SignRawTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.C } // ValidateAddress handles the validateaddress command. -func ValidateAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func ValidateAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.ValidateAddressCmd) - result := btcjson.ValidateAddressResult{} - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) + result := btcjson.ValidateAddressWalletResult{} + addr, err := decodeAddress(cmd.Address, activeNet.Params) if err != nil { // Use result zero value (IsValid=false). return result, nil @@ -2892,7 +3049,7 @@ func ValidateAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) result.IsMine = true acctName, err := w.Manager.AccountName(ainfo.Account()) if err != nil { - return nil, ErrAccountNameNotFound + return nil, &ErrAccountNameNotFound } result.Account = acctName @@ -2942,12 +3099,12 @@ func ValidateAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) // VerifyMessage handles the verifymessage command by verifying the provided // compact signature for the given address and message. -func VerifyMessage(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func VerifyMessage(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.VerifyMessageCmd) - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) + addr, err := decodeAddress(cmd.Address, activeNet.Params) if err != nil { - return nil, ParseError{err} + return nil, err } // decode base64 signature @@ -2985,14 +3142,14 @@ func VerifyMessage(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) ( // WalletIsLocked handles the walletislocked extension request by // returning the current lock state (false for unlocked, true for locked) // of an account. -func WalletIsLocked(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func WalletIsLocked(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { return w.Locked(), nil } // WalletLock handles a walletlock request by locking the all account // wallets, returning an error if any wallet is not encrypted (for example, // a watching-only wallet). -func WalletLock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func WalletLock(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { w.Lock() return nil, nil } @@ -3000,7 +3157,7 @@ func WalletLock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (int // WalletPassphrase responds to the walletpassphrase request by unlocking // the wallet. The decryption key is saved in the wallet until timeout // seconds expires, after which the wallet is locked. -func WalletPassphrase(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func WalletPassphrase(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.WalletPassphraseCmd) timeout := time.Second * time.Duration(cmd.Timeout) @@ -3015,13 +3172,16 @@ func WalletPassphrase(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd // // If the old passphrase is correct and the passphrase is changed, all // wallets will be immediately locked. -func WalletPassphraseChange(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { +func WalletPassphraseChange(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.WalletPassphraseChangeCmd) err := w.ChangePassphrase([]byte(cmd.OldPassphrase), []byte(cmd.NewPassphrase)) if isManagerWrongPassphraseError(err) { - return nil, btcjson.ErrWalletPassphraseIncorrect + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCWalletPassphraseIncorrect, + Message: "Incorrect passphrase", + } } return nil, err } @@ -3034,5 +3194,12 @@ func decodeHexStr(hexStr string) ([]byte, error) { if len(hexStr)%2 != 0 { hexStr = "0" + hexStr } - return hex.DecodeString(hexStr) + decoded, err := hex.DecodeString(hexStr) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCDecodeHexString, + Message: "Hex string decode failed: " + err.Error(), + } + } + return decoded, nil } diff --git a/rpcserverhelp.go b/rpcserverhelp.go new file mode 100644 index 0000000..865e0a6 --- /dev/null +++ b/rpcserverhelp.go @@ -0,0 +1,59 @@ +// AUTOGENERATED by internal/rpchelp/genrpcserverhelp.go; do not edit. + +package main + +func helpDescsEnUS() map[string]string { + return map[string]string{ + "addmultisigaddress": "addmultisigaddress nrequired [\"key\",...] (account=\"\")\n\nGenerates and imports a multisig address and redeeming script to the 'imported' account.\n\nArguments:\n1. nrequired (numeric, required) The number of signatures required to redeem outputs paid to this address\n2. keys (array of string, required) Pubkeys and/or pay-to-pubkey-hash addresses to partially control the multisig address\n3. account (string, optional, default=\"\") DEPRECATED -- Unused (all imported addresses belong to the imported account)\n\nResult:\n\"value\" (string) The imported pay-to-script-hash address\n", + "createmultisig": "createmultisig nrequired [\"key\",...]\n\nGenerate a multisig address and redeem script.\n\nArguments:\n1. nrequired (numeric, required) The number of signatures required to redeem outputs paid to this address\n2. keys (array of string, required) Pubkeys and/or pay-to-pubkey-hash addresses to partially control the multisig address\n\nResult:\n{\n \"address\": \"value\", (string) The generated pay-to-script-hash address\n \"redeemScript\": \"value\", (string) The script required to redeem outputs paid to the multisig address\n} \n", + "dumpprivkey": "dumpprivkey \"address\"\n\nReturns the private key in WIF encoding that controls some wallet address.\n\nArguments:\n1. address (string, required) The address to return a private key for\n\nResult:\n\"value\" (string) The WIF-encoded private key\n", + "getaccount": "getaccount \"address\"\n\nDEPRECATED -- Lookup the account name that some wallet address belongs to.\n\nArguments:\n1. address (string, required) The address to query the account for\n\nResult:\n\"value\" (string) The name of the account that 'address' belongs to\n", + "getaccountaddress": "getaccountaddress \"account\"\n\nDEPRECATED -- Returns the most recent external payment address for an account that has not been seen publicly.\nA new address is generated for the account if the most recently generated address has been seen on the blockchain or in mempool.\n\nArguments:\n1. account (string, required) The account of the returned address\n\nResult:\n\"value\" (string) The unused address for 'account'\n", + "getaddressesbyaccount": "getaddressesbyaccount \"account\"\n\nDEPRECATED -- Returns all addresses strings controlled by a single account.\n\nArguments:\n1. account (string, required) Account name to fetch addresses for\n\nResult:\n[\"value\",...] (array of string) All addresses controlled by 'account'\n", + "getbalance": "getbalance (account=\"*\" minconf=1)\n\nCalculates and returns the balance of one or all accounts.\n\nArguments:\n1. account (string, optional, default=\"*\") DEPRECATED -- The account name to query the balance for, or '*' to consider all accounts\n2. minconf (numeric, optional, default=1) Minimum number of block confirmations required before an unspent output's value is included in the balance\n\nResult (account!=*):\nn.nnn (numeric) The balance of 'account' valued in bitcoin\n\nResult (account=*):\nn.nnn (numeric) The balance of all accounts valued in bitcoin\n", + "getbestblockhash": "getbestblockhash\n\nReturns the hash of the newest block in the best chain that wallet has finished syncing with.\n\nArguments:\nNone\n\nResult:\n\"value\" (string) The hash of the most recent synced-to block\n", + "getblockcount": "getblockcount\n\nReturns the blockchain height of the newest block in the best chain that wallet has finished syncing with.\n\nArguments:\nNone\n\nResult:\nn.nnn (numeric) The blockchain height of the most recent synced-to block\n", + "getinfo": "getinfo\n\nReturns a JSON object containing various state info.\n\nArguments:\nNone\n\nResult:\n{\n \"version\": n, (numeric) The version of the server\n \"protocolversion\": n, (numeric) The latest supported protocol version\n \"walletversion\": n, (numeric) The version of the address manager database\n \"balance\": n.nnn, (numeric) The balance of all accounts calculated with one block confirmation\n \"blocks\": n, (numeric) The number of blocks processed\n \"timeoffset\": n, (numeric) The time offset\n \"connections\": n, (numeric) The number of connected peers\n \"proxy\": \"value\", (string) The proxy used by the server\n \"difficulty\": n.nnn, (numeric) The current target difficulty\n \"testnet\": true|false, (boolean) Whether or not server is using testnet\n \"keypoololdest\": n, (numeric) Unset\n \"keypoolsize\": n, (numeric) Unset\n \"unlocked_until\": n, (numeric) Unset\n \"paytxfee\": n.nnn, (numeric) The increment used each time more fee is required for an authored transaction\n \"relayfee\": n.nnn, (numeric) The minimum relay fee for non-free transactions in BTC/KB\n \"errors\": \"value\", (string) Any current errors\n} \n", + "getnewaddress": "getnewaddress (account=\"\")\n\nGenerates and returns a new payment address.\n\nArguments:\n1. account (string, optional, default=\"\") DEPRECATED -- Account name the new address will belong to\n\nResult:\n\"value\" (string) The payment address\n", + "getrawchangeaddress": "getrawchangeaddress (account=\"\")\n\nGenerates and returns a new internal payment address for use as a change address in raw transactions.\n\nArguments:\n1. account (string, optional, default=\"\") Account name the new internal address will belong to\n\nResult:\n\"value\" (string) The internal payment address\n", + "getreceivedbyaccount": "getreceivedbyaccount \"account\" (minconf=1)\n\nDEPRECATED -- Returns the total amount received by addresses of some account, including spent outputs.\n\nArguments:\n1. account (string, required) Account name to query total received amount for\n2. minconf (numeric, optional, default=1) Minimum number of block confirmations required before an output's value is included in the total\n\nResult:\nn.nnn (numeric) The total received amount valued in bitcoin\n", + "getreceivedbyaddress": "getreceivedbyaddress \"address\" (minconf=1)\n\nReturns the total amount received by a single address, including spent outputs.\n\nArguments:\n1. address (string, required) Payment address which received outputs to include in total\n2. minconf (numeric, optional, default=1) Minimum number of block confirmations required before an output's value is included in the total\n\nResult:\nn.nnn (numeric) The total received amount valued in bitcoin\n", + "gettransaction": "gettransaction \"txid\" (includewatchonly=false)\n\nReturns a JSON object with details regarding a transaction relevant to this wallet.\n\nArguments:\n1. txid (string, required) Hash of the transaction to query\n2. includewatchonly (boolean, optional, default=false) Also consider transactions involving watched addresses\n\nResult:\n{\n \"amount\": n.nnn, (numeric) The total amount this transaction credits to the wallet, valued in bitcoin\n \"fee\": n.nnn, (numeric) The total input value minus the total output value, or 0 if 'txid' is not a sent transaction\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction\n \"blockhash\": \"value\", (string) The hash of the block this transaction is mined in, or the empty string if unmined\n \"blockindex\": n, (numeric) Unset\n \"blocktime\": n, (numeric) The Unix time of the block header this transaction is mined in, or 0 if unmined\n \"txid\": \"value\", (string) The transaction hash\n \"walletconflicts\": [\"value\",...], (array of string) Unset\n \"time\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"timereceived\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"details\": [{ (array of object) Additional details for each recorded wallet credit and debit\n \"account\": \"value\", (string) DEPRECATED -- Unset\n \"address\": \"value\", (string) The address an output was paid to, or the empty string if the output is nonstandard or this detail is regarding a transaction input\n \"category\": \"value\", (string) The kind of detail: \"send\" for sent transactions, \"immature\" for immature coinbase outputs, \"generate\" for mature coinbase outputs, or \"recv\" for all other received outputs\n \"amount\": n.nnn, (numeric) The amount of a received output\n \"fee\": n.nnn, (numeric) The included fee for a sent transaction\n },...], \n \"hex\": \"value\", (string) The transaction encoded as a hexadecimal string\n} \n", + "help": "help (\"command\")\n\nReturns a list of all commands or help for a specified command.\n\nArguments:\n1. command (string, optional) The command to retrieve help for\n\nResult (no command provided):\n\"value\" (string) List of commands\n\nResult (command specified):\n\"value\" (string) Help for specified command\n", + "importprivkey": "importprivkey \"privkey\" (label=\"\" rescan=true)\n\nImports a WIF-encoded private key to the 'imported' account.\n\nArguments:\n1. privkey (string, required) The WIF-encoded private key\n2. label (string, optional, default=\"\") Unused (all imported addresses belong to the imported account)\n3. rescan (boolean, optional, default=true) Rescan the blockchain (since the genesis block) for outputs controlled by the imported key\n\nResult:\nNothing\n", + "keypoolrefill": "keypoolrefill (newsize=100)\n\nDEPRECATED -- This request does nothing since no keypool is maintained.\n\nArguments:\n1. newsize (numeric, optional, default=100) Unused\n\nResult:\nNothing\n", + "listaccounts": "listaccounts (minconf=1)\n\nDEPRECATED -- Returns a JSON object of all accounts and their balances.\n\nArguments:\n1. minconf (numeric, optional, default=1) Minimum number of block confirmations required before an unspent output's value is included in the balance\n\nResult:\n{\n \"The account name\": The account balance valued in bitcoin, (object) JSON object with account names as keys and bitcoin amounts as values\n ...\n}\n", + "listlockunspent": "listlockunspent\n\nReturns a JSON array of outpoints marked as locked (with lockunspent) for this wallet session.\n\nArguments:\nNone\n\nResult:\n[{\n \"txid\": \"value\", (string) The transaction hash of the referenced output\n \"vout\": n, (numeric) The output index of the referenced output\n},...]\n", + "listreceivedbyaccount": "listreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\n\nDEPRECATED -- Returns a JSON array of objects listing all accounts and the total amount received by each account.\n\nArguments:\n1. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction is considered\n2. includeempty (boolean, optional, default=false) Unused\n3. includewatchonly (boolean, optional, default=false) Unused\n\nResult:\n[{\n \"account\": \"value\", (string) The name of the account\n \"amount\": n.nnn, (numeric) Total amount received by payment addresses of the account valued in bitcoin\n \"confirmations\": n, (numeric) Number of block confirmations of the most recent transaction relevant to the account\n},...]\n", + "listreceivedbyaddress": "listreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\n\nReturns a JSON array of objects listing wallet payment addresses and their total received amounts.\n\nArguments:\n1. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction is considered\n2. includeempty (boolean, optional, default=false) Unused\n3. includewatchonly (boolean, optional, default=false) Unused\n\nResult:\n[{\n \"account\": \"value\", (string) DEPRECATED -- Unset\n \"address\": \"value\", (string) The payment address\n \"amount\": n.nnn, (numeric) Total amount received by the payment address valued in bitcoin\n \"confirmations\": n, (numeric) Number of block confirmations of the most recent transaction relevant to the address\n \"txids\": [\"value\",...], (array of string) Transaction hashes of all transactions involving this address\n \"involvesWatchonly\": true|false, (boolean) Unset\n},...]\n", + "listsinceblock": "listsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\n\nReturns a JSON array of objects listing details of wallet transactions after some block.\n\nArguments:\n1. blockhash (string, optional) Hash of the parent block of the first block to consider transactions from\n2. targetconfirmations (numeric, optional, default=1) Minimum number of block confirmations required before a transaction is considered\n3. includewatchonly (boolean, optional, default=false) Unused\n\nResult:\n{\n \"transactions\": [{ (array of object) JSON array of objects containing verbose details of the each transaction\n \"account\": \"value\", (string) DEPRECATED -- Unset\n \"address\": \"value\", (string) Payment address for a transaction output\n \"category\": \"value\", (string) The kind of transaction: \"send\" for sent transactions, \"immature\" for immature coinbase outputs, \"generate\" for mature coinbase outputs, or \"recv\" for all other received outputs. Note: A single output may be included multiple times under different categories\n \"amount\": n.nnn, (numeric) The value of the transaction output valued in bitcoin\n \"fee\": n.nnn, (numeric) The total input value minus the total output value for sent transactions\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction\n \"generated\": true|false, (boolean) Whether the transaction output is a coinbase output\n \"blockhash\": \"value\", (string) The hash of the block this transaction is mined in, or the empty string if unmined\n \"blockindex\": n, (numeric) Unset\n \"blocktime\": n, (numeric) The Unix time of the block header this transaction is mined in, or 0 if unmined\n \"txid\": \"value\", (string) The hash of the transaction\n \"walletconflicts\": [\"value\",...], (array of string) Unset\n \"time\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"timereceived\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"comment\": \"value\", (string) Unset\n \"otheraccount\": \"value\", (string) Unset\n },...], \n \"lastblock\": \"value\", (string) Hash of the latest-synced block to be used in later calls to listsinceblock\n} \n", + "listtransactions": "listtransactions (\"account\" count=10 from=0 includewatchonly=false)\n\nReturns a JSON array of objects containing verbose details for wallet transactions.\n\nArguments:\n1. account (string, optional) DEPRECATED -- Unused\n2. count (numeric, optional, default=10) Maximum number of transactions to create results from\n3. from (numeric, optional, default=0) Number of transactions to skip before results are created\n4. includewatchonly (boolean, optional, default=false) Unused\n\nResult:\n[{\n \"account\": \"value\", (string) DEPRECATED -- Unset\n \"address\": \"value\", (string) Payment address for a transaction output\n \"category\": \"value\", (string) The kind of transaction: \"send\" for sent transactions, \"immature\" for immature coinbase outputs, \"generate\" for mature coinbase outputs, or \"recv\" for all other received outputs. Note: A single output may be included multiple times under different categories\n \"amount\": n.nnn, (numeric) The value of the transaction output valued in bitcoin\n \"fee\": n.nnn, (numeric) The total input value minus the total output value for sent transactions\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction\n \"generated\": true|false, (boolean) Whether the transaction output is a coinbase output\n \"blockhash\": \"value\", (string) The hash of the block this transaction is mined in, or the empty string if unmined\n \"blockindex\": n, (numeric) Unset\n \"blocktime\": n, (numeric) The Unix time of the block header this transaction is mined in, or 0 if unmined\n \"txid\": \"value\", (string) The hash of the transaction\n \"walletconflicts\": [\"value\",...], (array of string) Unset\n \"time\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"timereceived\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"comment\": \"value\", (string) Unset\n \"otheraccount\": \"value\", (string) Unset\n},...]\n", + "listunspent": "listunspent (minconf=1 maxconf=9999999 [\"address\",...])\n\nReturns a JSON array of objects representing unlocked unspent outputs controlled by wallet keys.\n\nArguments:\n1. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction output is considered\n2. maxconf (numeric, optional, default=9999999) Maximum number of block confirmations required before a transaction output is excluded\n3. addresses (array of string, optional) If set, limits the returned details to unspent outputs received by any of these payment addresses\n\nResult:\n{\n \"txid\": \"value\", (string) The transaction hash of the referenced output\n \"vout\": n, (numeric) The output index of the referenced output\n \"address\": \"value\", (string) The payment address that received the output\n \"account\": \"value\", (string) The account associated with the receiving payment address\n \"scriptPubKey\": \"value\", (string) The output script encoded as a hexadecimal string\n \"redeemScript\": \"value\", (string) Unset\n \"amount\": n.nnn, (numeric) The amount of the output valued in bitcoin\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction\n} \n", + "lockunspent": "lockunspent unlock [{\"txid\":\"value\",\"vout\":n},...]\n\nLocks or unlocks an unspent output.\nLocked outputs are not chosen for transaction inputs of authored transactions and are not included in 'listunspent' results.\nLocked outputs are volatile and are not saved across wallet restarts.\nIf unlock is true and no transaction outputs are specified, all locked outputs are marked unlocked.\n\nArguments:\n1. unlock (boolean, required) True to unlock outputs, false to lock\n2. transactions (array of object, required) Transaction outputs to lock or unlock\n[{\n \"txid\": \"value\", (string) The transaction hash of the referenced output\n \"vout\": n, (numeric) The output index of the referenced output\n},...]\n\nResult:\ntrue|false (boolean) The boolean 'true'\n", + "sendfrom": "sendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\n\nDEPRECATED -- Authors, signs, and sends a transaction that outputs some amount to a payment address.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. fromaccount (string, required) Account to pick unspent outputs from\n2. toaddress (string, required) Address to pay\n3. amount (numeric, required) Amount to send to the payment address valued in bitcoin\n4. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction output is eligible to be spent\n5. comment (string, optional) Unused\n6. commentto (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", + "sendmany": "sendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\n\nAuthors, signs, and sends a transaction that outputs to many payment addresses.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. fromaccount (string, required) DEPRECATED -- Account to pick unspent outputs from\n2. amounts (object, required) Pairs of payment addresses and the output amount to pay each\n{\n \"Address to pay\": Amount to send to the payment address valued in bitcoin, (object) JSON object using payment addresses as keys and output amounts valued in bitcoin to send to each address\n ...\n}\n3. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction output is eligible to be spent\n4. comment (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", + "sendtoaddress": "sendtoaddress \"address\" amount (\"comment\" \"commentto\")\n\nAuthors, signs, and sends a transaction that outputs some amount to a payment address.\nUnlike sendfrom, outputs are always chosen from the default account.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. address (string, required) Address to pay\n2. amount (numeric, required) Amount to send to the payment address valued in bitcoin\n3. comment (string, optional) Unused\n4. commentto (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", + "settxfee": "settxfee amount\n\nModify the increment used each time more fee is required for an authored transaction.\n\nArguments:\n1. amount (numeric, required) The new fee increment valued in bitcoin\n\nResult:\ntrue|false (boolean) The boolean 'true'\n", + "signmessage": "signmessage \"address\" \"message\"\n\nSigns a message using the private key of a payment address.\n\nArguments:\n1. address (string, required) Payment address of private key used to sign the message with\n2. message (string, required) Message to sign\n\nResult:\n\"value\" (string) The signed message encoded as a base64 string\n", + "signrawtransaction": "signrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\n\nSigns transaction inputs using private keys from this wallet and request.\nThe valid flags options are ALL, NONE, SINGLE, ALL|ANYONECANPAY, NONE|ANYONECANPAY, and SINGLE|ANYONECANPAY.\n\nArguments:\n1. rawtx (string, required) Unsigned or partially unsigned transaction to sign encoded as a hexadecimal string\n2. inputs (array of object, optional) Additional data regarding inputs that this wallet may not be tracking\n3. privkeys (array of string, optional) Additional WIF-encoded private keys to use when creating signatures\n4. flags (string, optional, default=\"ALL\") Sighash flags\n\nResult:\n{\n \"hex\": \"value\", (string) The resulting transaction encoded as a hexadecimal string\n \"complete\": true|false, (boolean) Whether all input signatures have been created\n} \n", + "validateaddress": "validateaddress \"address\"\n\nVerify that an address is valid.\nExtra details are returned if the address is controlled by this wallet.\nThe following fields are valid only when the address is controlled by this wallet (ismine=true): isscript, pubkey, iscompressed, account, addresses, hex, script, and sigsrequired.\nThe following fields are only valid when address has an associated public key: pubkey, iscompressed.\nThe following fields are only valid when address is a pay-to-script-hash address: addresses, hex, and script.\nIf the address is a multisig address controlled by this wallet, the multisig fields will be left unset if the wallet is locked since the redeem script cannot be decrypted.\n\nArguments:\n1. address (string, required) Address to validate\n\nResult:\n{\n \"isvalid\": true|false, (boolean) Whether or not the address is valid\n \"address\": \"value\", (string) The payment address (only when isvalid is true)\n \"ismine\": true|false, (boolean) Whether this address is controlled by the wallet (only when isvalid is true)\n \"iswatchonly\": true|false, (boolean) Unset\n \"isscript\": true|false, (boolean) Whether the payment address is a pay-to-script-hash address (only when isvalid is true)\n \"pubkey\": \"value\", (string) The associated public key of the payment address, if any (only when isvalid is true)\n \"iscompressed\": true|false, (boolean) Whether the address was created by hashing a compressed public key, if any (only when isvalid is true)\n \"account\": \"value\", (string) The account this payment address belongs to (only when isvalid is true)\n \"addresses\": [\"value\",...], (array of string) All associated payment addresses of the script if address is a multisig address (only when isvalid is true)\n \"hex\": \"value\", (string) The redeem script \n \"script\": \"value\", (string) The class of redeem script for a multisig address\n \"sigsrequired\": n, (numeric) The number of required signatures to redeem outputs to the multisig address\n} \n", + "verifymessage": "verifymessage \"address\" \"signature\" \"message\"\n\nVerify a message was signed with the associated private key of some address.\n\nArguments:\n1. address (string, required) Address used to sign message\n2. signature (string, required) The signature to verify\n3. message (string, required) The message to verify\n\nResult:\ntrue|false (boolean) Whether the message was signed with the private key of 'address'\n", + "walletlock": "walletlock\n\nLock the wallet.\n\nArguments:\nNone\n\nResult:\nNothing\n", + "walletpassphrase": "walletpassphrase \"passphrase\" timeout\n\nUnlock the wallet.\n\nArguments:\n1. passphrase (string, required) The wallet passphrase\n2. timeout (numeric, required) The number of seconds to wait before the wallet automatically locks\n\nResult:\nNothing\n", + "walletpassphrasechange": "walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n\nChange the wallet passphrase.\n\nArguments:\n1. oldpassphrase (string, required) The old wallet passphrase\n2. newpassphrase (string, required) The new wallet passphrase\n\nResult:\nNothing\n", + "createnewaccount": "createnewaccount \"account\"\n\nCreates a new account.\nThe wallet must be unlocked for this request to succeed.\n\nArguments:\n1. account (string, required) Name of the new account\n\nResult:\nNothing\n", + "exportwatchingwallet": "exportwatchingwallet (account=\"\" download=false)\n\nCreates and returns a duplicate of the wallet database without any private keys to be used as a watching-only wallet.\n\nArguments:\n1. account (string, optional, default=\"\") Unused\n2. download (boolean, optional, default=false) Unused\n\nResult:\n\"value\" (string) The watching-only database encoded as a base64 string\n", + "getbestblock": "getbestblock\n\nReturns the hash and height of the newest block in the best chain that wallet has finished syncing with.\n\nArguments:\nNone\n\nResult:\n{\n \"hash\": \"value\", (string) The hash of the block\n \"height\": n, (numeric) The blockchain height of the block\n} \n", + "getunconfirmedbalance": "getunconfirmedbalance (account=\"\")\n\nCalculates the unspent output value of all unmined transaction outputs for an account.\n\nArguments:\n1. account (string, optional, default=\"\") The account to query the unconfirmed balance for\n\nResult:\nn.nnn (numeric) Total amount of all unmined unspent outputs of the account valued in bitcoin.\n", + "listaddresstransactions": "listaddresstransactions [\"address\",...] (account=\"\")\n\nReturns a JSON array of objects containing verbose details for wallet transactions pertaining some addresses.\n\nArguments:\n1. addresses (array of string, required) Addresses to filter transaction results by\n2. account (string, optional, default=\"\") Unused\n\nResult:\n[{\n \"account\": \"value\", (string) DEPRECATED -- Unset\n \"address\": \"value\", (string) Payment address for a transaction output\n \"category\": \"value\", (string) The kind of transaction: \"send\" for sent transactions, \"immature\" for immature coinbase outputs, \"generate\" for mature coinbase outputs, or \"recv\" for all other received outputs. Note: A single output may be included multiple times under different categories\n \"amount\": n.nnn, (numeric) The value of the transaction output valued in bitcoin\n \"fee\": n.nnn, (numeric) The total input value minus the total output value for sent transactions\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction\n \"generated\": true|false, (boolean) Whether the transaction output is a coinbase output\n \"blockhash\": \"value\", (string) The hash of the block this transaction is mined in, or the empty string if unmined\n \"blockindex\": n, (numeric) Unset\n \"blocktime\": n, (numeric) The Unix time of the block header this transaction is mined in, or 0 if unmined\n \"txid\": \"value\", (string) The hash of the transaction\n \"walletconflicts\": [\"value\",...], (array of string) Unset\n \"time\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"timereceived\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"comment\": \"value\", (string) Unset\n \"otheraccount\": \"value\", (string) Unset\n},...]\n", + "listalltransactions": "listalltransactions (account=\"\")\n\nReturns a JSON array of objects in the same format as 'listtransactions' without limiting the number of returned objects.\n\nArguments:\n1. account (string, optional, default=\"\") Unused\n\nResult:\n[{\n \"account\": \"value\", (string) DEPRECATED -- Unset\n \"address\": \"value\", (string) Payment address for a transaction output\n \"category\": \"value\", (string) The kind of transaction: \"send\" for sent transactions, \"immature\" for immature coinbase outputs, \"generate\" for mature coinbase outputs, or \"recv\" for all other received outputs. Note: A single output may be included multiple times under different categories\n \"amount\": n.nnn, (numeric) The value of the transaction output valued in bitcoin\n \"fee\": n.nnn, (numeric) The total input value minus the total output value for sent transactions\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction\n \"generated\": true|false, (boolean) Whether the transaction output is a coinbase output\n \"blockhash\": \"value\", (string) The hash of the block this transaction is mined in, or the empty string if unmined\n \"blockindex\": n, (numeric) Unset\n \"blocktime\": n, (numeric) The Unix time of the block header this transaction is mined in, or 0 if unmined\n \"txid\": \"value\", (string) The hash of the transaction\n \"walletconflicts\": [\"value\",...], (array of string) Unset\n \"time\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"timereceived\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"comment\": \"value\", (string) Unset\n \"otheraccount\": \"value\", (string) Unset\n},...]\n", + "renameaccount": "renameaccount \"oldaccount\" \"newaccount\"\n\nRenames an account.\n\nArguments:\n1. oldaccount (string, required) The old account name to rename\n2. newaccount (string, required) The new name for the account\n\nResult:\nNothing\n", + "walletislocked": "walletislocked\n\nReturns whether or not the wallet is locked.\n\nArguments:\nNone\n\nResult:\ntrue|false (boolean) Whether the wallet is locked\n", + } +} + +var localeHelpDescs = map[string]func() map[string]string{ + "en_US": helpDescsEnUS, +} + +var requestUsages = "addmultisigaddress nrequired [\"key\",...] (account=\"\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (account=\"*\" minconf=1)\ngetbestblockhash\ngetblockcount\ngetinfo\ngetnewaddress (account=\"\")\ngetrawchangeaddress (account=\"\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (label=\"\" rescan=true)\nkeypoolrefill (newsize=100)\nlistaccounts (minconf=1)\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"txid\":\"value\",\"vout\":n},...]\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\ncreatenewaccount \"account\"\nexportwatchingwallet (account=\"\" download=false)\ngetbestblock\ngetunconfirmedbalance (account=\"\")\nlistaddresstransactions [\"address\",...] (account=\"\")\nlistalltransactions (account=\"\")\nrenameaccount \"oldaccount\" \"newaccount\"\nwalletislocked" diff --git a/wallet/wallet.go b/wallet/wallet.go index ceff9f9..26dfd97 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -30,7 +30,7 @@ import ( "time" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcjson/v2/btcjson" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -1115,7 +1115,7 @@ func (s creditSlice) Swap(i, j int) { // contained within it will be considered. If we know nothing about a // transaction an empty array will be returned. func (w *Wallet) ListUnspent(minconf, maxconf int32, - addresses map[string]bool) ([]*btcjson.ListUnspentResult, error) { + addresses map[string]struct{}) ([]*btcjson.ListUnspentResult, error) { syncBlock := w.Manager.SyncedTo() @@ -1184,7 +1184,7 @@ func (w *Wallet) ListUnspent(minconf, maxconf int32, } include: result := &btcjson.ListUnspentResult{ - TxId: output.OutPoint.Hash.String(), + TxID: output.OutPoint.Hash.String(), Vout: output.OutPoint.Index, Account: acctName, ScriptPubKey: hex.EncodeToString(output.PkScript),