diff --git a/internal/rpchelp/helpdescs_en_US.go b/internal/rpchelp/helpdescs_en_US.go index 3c16bf9..4dc3629 100644 --- a/internal/rpchelp/helpdescs_en_US.go +++ b/internal/rpchelp/helpdescs_en_US.go @@ -57,9 +57,9 @@ var helpDescsEnUS = map[string]string{ // 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-account": "DEPRECATED -- The account name to query the balance for, or \"*\" to consider all accounts (default=\"*\")", + "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", @@ -94,12 +94,12 @@ var helpDescsEnUS = map[string]string{ // GetNewAddressCmd help. "getnewaddress--synopsis": "Generates and returns a new payment address.", - "getnewaddress-account": "DEPRECATED -- Account name the new address will belong to", + "getnewaddress-account": "DEPRECATED -- Account name the new address will belong to (default=\"default\")", "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-account": "Account name the new internal address will belong to (default=\"default\")", "getrawchangeaddress--result0": "The internal payment address", // GetReceivedByAccountCmd help. @@ -151,7 +151,7 @@ var helpDescsEnUS = map[string]string{ // 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-label": "Unused (must be unset or 'imported')", "importprivkey-rescan": "Rescan the blockchain (since the genesis block) for outputs controlled by the imported key", // KeypoolRefillCmd help. @@ -227,7 +227,7 @@ var helpDescsEnUS = map[string]string{ // ListTransactionsCmd help. "listtransactions--synopsis": "Returns a JSON array of objects containing verbose details for wallet transactions.", - "listtransactions-account": "DEPRECATED -- Unused", + "listtransactions-account": "DEPRECATED -- Unused (must be unset or \"*\")", "listtransactions-count": "Maximum number of transactions to create results from", "listtransactions-from": "Number of transactions to skip before results are created", "listtransactions-includewatchonly": "Unused", @@ -363,7 +363,7 @@ var helpDescsEnUS = map[string]string{ // 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-account": "Unused (must be unset or \"*\")", "exportwatchingwallet-download": "Unused", "exportwatchingwallet--result0": "The watching-only database encoded as a base64 string", @@ -376,17 +376,17 @@ var helpDescsEnUS = map[string]string{ // 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-account": "The account to query the unconfirmed balance for (default=\"default\")", "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", + "listaddresstransactions-account": "Unused (must be unset or \"*\")", // 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", + "listalltransactions-account": "Unused (must be unset or \"*\")", // RenameAccountCmd help. "renameaccount--synopsis": "Renames an account.", diff --git a/rpcserver.go b/rpcserver.go index a6bd4e7..0be583c 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -112,6 +112,11 @@ var ( Code: btcjson.ErrRPCNoTxInfo, Message: "No information for transaction", } + + ErrReservedAccountName = btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidParameter, + Message: "Account name is reserved by RPC server", + } ) // TODO(jrick): There are several error paths which 'replace' various errors @@ -1608,6 +1613,15 @@ func DumpWallet(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (int // 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) { + cmd := icmd.(*btcjson.ExportWatchingWalletCmd) + + if cmd.Account != nil && *cmd.Account != "*" { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCWallet, + Message: "Individual accounts can not be exported as watching-only", + } + } + return w.ExportWatchingWallet(cfg.WalletPass) } @@ -1643,7 +1657,10 @@ func GetBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (int var balance btcutil.Amount var err error - accountName := *cmd.Account + accountName := "*" + if cmd.Account != nil { + accountName = *cmd.Account + } if accountName == "*" { balance, err = w.CalculateBalance(int32(*cmd.MinConf)) } else { @@ -1783,11 +1800,14 @@ func GetAccountAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{ func GetUnconfirmedBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetUnconfirmedBalanceCmd) - account, err := w.Manager.LookupAccount(*cmd.Account) + acctName := "default" + if cmd.Account != nil { + acctName = *cmd.Account + } + account, err := w.Manager.LookupAccount(acctName) if err != nil { return nil, err } - unconfirmed, err := w.CalculateAccountBalance(account, 0) if err != nil { return nil, err @@ -1808,7 +1828,7 @@ func ImportPrivKey(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) ( // Ensure that private keys are only imported to the correct account. // // Yes, Label is the account name. - if *cmd.Label != waddrmgr.ImportedAddrAccountName { + if cmd.Label != nil && *cmd.Label != waddrmgr.ImportedAddrAccountName { return nil, &ErrNotImportedAccount } @@ -1851,6 +1871,12 @@ func KeypoolRefill(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) ( func CreateNewAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.CreateNewAccountCmd) + // The wildcard * is reserved by the rpc server with the special meaning + // of "all accounts", so disallow naming accounts to this string. + if cmd.Account == "*" { + return nil, &ErrReservedAccountName + } + // Check that we are within the maximum allowed non-empty accounts limit. account, err := w.Manager.LastAccount() if err != nil { @@ -1882,6 +1908,13 @@ func CreateNewAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{} // If the account does not exist an appropiate error will be returned. func RenameAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.RenameAccountCmd) + + // The wildcard * is reserved by the rpc server with the special meaning + // of "all accounts", so disallow naming accounts to this string. + if cmd.NewAccount == "*" { + return nil, &ErrReservedAccountName + } + // Check that given account exists account, err := w.Manager.LookupAccount(cmd.OldAccount) if err != nil { @@ -1898,11 +1931,14 @@ func RenameAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) ( func GetNewAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetNewAddressCmd) - account, err := w.Manager.LookupAccount(*cmd.Account) + acctName := "default" + if cmd.Account != nil { + acctName = *cmd.Account + } + account, err := w.Manager.LookupAccount(acctName) if err != nil { return nil, err } - addr, err := w.NewAddress(account) if err != nil { return nil, err @@ -1919,7 +1955,12 @@ func GetNewAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) ( // but ignores the parameter. func GetRawChangeAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.GetRawChangeAddressCmd) - account, err := w.Manager.LookupAccount(*cmd.Account) + + acctName := "default" + if cmd.Account != nil { + acctName = *cmd.Account + } + account, err := w.Manager.LookupAccount(acctName) if err != nil { return nil, err } @@ -2424,7 +2465,7 @@ func ListTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{} // between transactions pertaining to one account from another. This // will be resolved when wtxmgr is combined with the waddrmgr namespace. - if cmd.Account != nil { + if cmd.Account != nil && *cmd.Account != "*" { // For now, don't bother trying to continue if the user // specified an account, since this can't be (easily or // efficiently) calculated. @@ -2445,6 +2486,13 @@ func ListTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{} func ListAddressTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd interface{}) (interface{}, error) { cmd := icmd.(*btcjson.ListAddressTransactionsCmd) + if cmd.Account != nil && *cmd.Account != "*" { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidParameter, + Message: "Listing transactions for addresses may only be done for all accounts", + } + } + // Decode addresses. hash160Map := make(map[string]struct{}) for _, addrStr := range cmd.Addresses { @@ -2463,6 +2511,15 @@ func ListAddressTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd inte // 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 interface{}) (interface{}, error) { + cmd := icmd.(*btcjson.ListAllTransactionsCmd) + + if cmd.Account != nil && *cmd.Account != "*" { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidParameter, + Message: "Listing all transactions may only be done for all accounts", + } + } + return w.ListAllTransactions() } diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 865e0a6..91ef49e 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -4,30 +4,30 @@ 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", + "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) 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", + "getbalance": "getbalance (\"account\" minconf=1)\n\nCalculates and returns the balance of one or all accounts.\n\nArguments:\n1. account (string, optional) DEPRECATED -- The account name to query the balance for, or \"*\" to consider all accounts (default=\"*\")\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", + "getnewaddress": "getnewaddress (\"account\")\n\nGenerates and returns a new payment address.\n\nArguments:\n1. account (string, optional) DEPRECATED -- Account name the new address will belong to (default=\"default\")\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) Account name the new internal address will belong to (default=\"default\")\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", + "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) Unused (must be unset or 'imported')\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", + "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 (must be unset or \"*\")\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", @@ -42,11 +42,11 @@ func helpDescsEnUS() map[string]string { "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", + "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) Unused (must be unset or \"*\")\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", + "getunconfirmedbalance": "getunconfirmedbalance (\"account\")\n\nCalculates the unspent output value of all unmined transaction outputs for an account.\n\nArguments:\n1. account (string, optional) The account to query the unconfirmed balance for (default=\"default\")\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) Unused (must be unset or \"*\")\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) Unused (must be unset or \"*\")\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", } @@ -56,4 +56,4 @@ 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" +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/waddrmgr/db.go b/waddrmgr/db.go index 717d4bf..29cba1d 100644 --- a/waddrmgr/db.go +++ b/waddrmgr/db.go @@ -17,6 +17,7 @@ package waddrmgr import ( + "bytes" "encoding/binary" "fmt" "time" @@ -30,7 +31,7 @@ import ( const ( // LatestMgrVersion is the most recent manager version. - LatestMgrVersion = 3 + LatestMgrVersion = 4 ) var ( @@ -1661,7 +1662,7 @@ func upgradeToVersion2(namespace walletdb.Namespace) error { // upgradeManager upgrades the data in the provided manager namespace to newer // versions as neeeded. -func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, config *Options) error { +func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainParams *chaincfg.Params, config *Options) error { var version uint32 err := namespace.View(func(tx walletdb.Tx) error { var err error @@ -1728,7 +1729,7 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, config * return err } // Upgrade from version 2 to 3. - if err := upgradeToVersion3(namespace, seed, privPassPhrase, pubPassPhrase); err != nil { + if err := upgradeToVersion3(namespace, seed, privPassPhrase, pubPassPhrase, chainParams); err != nil { return err } @@ -1736,6 +1737,15 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, config * version = 3 } + if version < 4 { + if err := upgradeToVersion4(namespace, pubPassPhrase); err != nil { + return err + } + + // The manager is now at version 4. + version = 4 + } + // Ensure the manager is upraded to the latest version. This check is // to intentionally cause a failure if the manager version is updated // without writing code to handle the upgrade. @@ -1754,12 +1764,12 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, config * // * acctNameIdxBucketName // * acctIDIdxBucketName // * metaBucketName -func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPassPhrase []byte) error { +func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPassPhrase []byte, chainParams *chaincfg.Params) error { err := namespace.Update(func(tx walletdb.Tx) error { currentMgrVersion := uint32(3) rootBucket := tx.RootBucket() - woMgr, err := loadManager(namespace, pubPassPhrase, &chaincfg.SimNetParams, nil) + woMgr, err := loadManager(namespace, pubPassPhrase, chainParams, nil) if err != nil { return err } @@ -1778,7 +1788,7 @@ func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPa } // Derive the cointype key according to BIP0044. - coinTypeKeyPriv, err := deriveCoinTypeKey(root, chaincfg.SimNetParams.HDCoinType) + coinTypeKeyPriv, err := deriveCoinTypeKey(root, chainParams.HDCoinType) if err != nil { str := "failed to derive cointype extended key" return managerError(ErrKeyChain, str, err) @@ -1833,10 +1843,10 @@ func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPa } // Update default account indexes - if err := putAccountIDIndex(tx, DefaultAccountNum, DefaultAccountName); err != nil { + if err := putAccountIDIndex(tx, DefaultAccountNum, defaultAccountName); err != nil { return err } - if err := putAccountNameIndex(tx, DefaultAccountNum, DefaultAccountName); err != nil { + if err := putAccountNameIndex(tx, DefaultAccountNum, defaultAccountName); err != nil { return err } // Update imported account indexes @@ -1860,3 +1870,93 @@ func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPa } return nil } + +// upgradeToVersion4 upgrades the database from version 3 to version 4. The +// default account remains unchanged (even if it was modified by the user), but +// the empty string alias to the default account is removed. +func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error { + err := namespace.Update(func(tx walletdb.Tx) error { + // Write new manager version. + err := putManagerVersion(tx, 4) + if err != nil { + return err + } + + // Lookup the old account info to determine the real name of the + // default account. All other names will be removed. + acctInfoIface, err := fetchAccountInfo(tx, DefaultAccountNum) + if err != nil { + return err + } + acctInfo, ok := acctInfoIface.(*dbBIP0044AccountRow) + if !ok { + str := fmt.Sprintf("unsupported account type %T", acctInfoIface) + return managerError(ErrDatabase, str, nil) + } + + var oldName string + + // Delete any other names for the default account. + c := tx.RootBucket().Bucket(acctNameIdxBucketName).Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + // Skip nested buckets. + if v == nil { + continue + } + + // Skip account names which aren't for the default account. + account := binary.LittleEndian.Uint32(v) + if account != DefaultAccountNum { + continue + } + + if !bytes.Equal(k[4:], []byte(acctInfo.name)) { + err := c.Delete() + if err != nil { + const str = "error deleting default account alias" + return managerError(ErrUpgrade, str, err) + } + oldName = string(k[4:]) + break + } + } + + // Ensure that the "default" name maps forwards and backwards to + // the default account index. + name, err := fetchAccountName(tx, DefaultAccountNum) + if err != nil { + return err + } + if name != acctInfo.name { + const str = "account name index does not map default account number to correct name" + return managerError(ErrUpgrade, str, nil) + } + acct, err := fetchAccountByName(tx, acctInfo.name) + if err != nil { + return err + } + if acct != DefaultAccountNum { + const str = "default account not accessible under correct name" + return managerError(ErrUpgrade, str, nil) + } + + // Ensure that looking up the default account by the old name + // cannot succeed. + _, err = fetchAccountByName(tx, oldName) + if err == nil { + const str = "default account exists under old name" + return managerError(ErrUpgrade, str, nil) + } else { + merr, ok := err.(ManagerError) + if !ok || merr.ErrorCode != ErrAccountNotFound { + return err + } + } + + return nil + }) + if err != nil { + return maybeConvertDbError(err) + } + return nil +} diff --git a/waddrmgr/internal_test.go b/waddrmgr/internal_test.go index 1a2def2..b4c0c97 100644 --- a/waddrmgr/internal_test.go +++ b/waddrmgr/internal_test.go @@ -91,3 +91,6 @@ func TstRunWithFailingCryptoKeyPriv(m *Manager, callback func()) { m.cryptoKeyPriv = &failingCryptoKey{} callback() } + +// TstDefaultAccountName is the constant defaultAccountName exported for tests. +const TstDefaultAccountName = defaultAccountName diff --git a/waddrmgr/manager.go b/waddrmgr/manager.go index ded3656..32b9035 100644 --- a/waddrmgr/manager.go +++ b/waddrmgr/manager.go @@ -57,8 +57,14 @@ const ( // DefaultAccountNum is the number of the default account. DefaultAccountNum = 0 - // DefaultAccountName is the name of the default account. - DefaultAccountName = "default" + // defaultAccountName is the initial name of the default account. Note + // that the default account may be renamed and is not a reserved name, + // so the default account might not be named "default" and non-default + // accounts may be named "default". + // + // Account numbers never change, so the DefaultAccountNum should be used + // to refer to (and only to) the default account. + defaultAccountName = "default" // The hierarchy described by BIP0043 is: // m/'/* @@ -88,15 +94,18 @@ const ( saltSize = 32 ) -var ( - // reservedAccountNames is a set of account names reserved for internal - // purposes - reservedAccountNames = map[string]struct{}{ - "*": struct{}{}, - DefaultAccountName: struct{}{}, - ImportedAddrAccountName: struct{}{}, - } -) +// isReservedAccountName returns true if the account name is reserved. Reserved +// accounts may never be renamed, and other accounts may not be renamed to a +// reserved name. +func isReservedAccountName(name string) bool { + return name == ImportedAddrAccountName +} + +// isReservedAccountNum returns true if the account number is reserved. +// Reserved accounts may not be renamed. +func isReservedAccountNum(acct uint32) bool { + return acct == ImportedAddrAccount +} // Options is used to hold the optional parameters passed to Create or Load. type Options struct { @@ -1630,11 +1639,7 @@ func (m *Manager) LastInternalAddress(account uint32) (ManagedAddress, error) { // ValidateAccountName validates the given account name and returns an error, if any. func ValidateAccountName(name string) error { - if name == "" { - str := "invalid account name, cannot be blank" - return managerError(ErrInvalidAccount, str, nil) - } - if _, ok := reservedAccountNames[name]; ok { + if isReservedAccountName(name) { str := "reserved account name" return managerError(ErrInvalidAccount, str, nil) } @@ -1748,6 +1753,12 @@ func (m *Manager) RenameAccount(account uint32, name string) error { m.mtx.Lock() defer m.mtx.Unlock() + // Ensure that a reserved account is not being renamed. + if isReservedAccountNum(account) { + str := "reserved account cannot be renamed" + return managerError(ErrInvalidAccount, str, nil) + } + // Check that account with the new name does not exist _, err := m.lookupAccount(name) if err == nil { @@ -2201,7 +2212,7 @@ func Open(namespace walletdb.Namespace, pubPassphrase []byte, chainParams *chain } // Upgrade the manager to the latest version as needed. - if err := upgradeManager(namespace, pubPassphrase, config); err != nil { + if err := upgradeManager(namespace, pubPassphrase, chainParams, config); err != nil { return nil, err } @@ -2453,13 +2464,8 @@ func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase [] // Save the information for the default account to the database. err = putAccountInfo(tx, DefaultAccountNum, acctPubEnc, - acctPrivEnc, 0, 0, DefaultAccountName) - if err != nil { - return err - } - - // Save "" alias for default account name for backward compat - return putAccountNameIndex(tx, DefaultAccountNum, "") + acctPrivEnc, 0, 0, defaultAccountName) + return err }) if err != nil { return nil, maybeConvertDbError(err) diff --git a/waddrmgr/manager_test.go b/waddrmgr/manager_test.go index 5719cc7..9e8ad96 100644 --- a/waddrmgr/manager_test.go +++ b/waddrmgr/manager_test.go @@ -1150,7 +1150,7 @@ func testNewAccount(tc *testContext) bool { return false } // Test account name validation - testName = "*" + testName = "imported" // A reserved account name _, err = tc.manager.NewAccount(testName) wantErrCode = waddrmgr.ErrInvalidAccount if !checkManagerError(tc.t, testName, err, wantErrCode) { @@ -1164,7 +1164,7 @@ func testNewAccount(tc *testContext) bool { func testLookupAccount(tc *testContext) bool { // Lookup accounts created earlier in testNewAccount expectedAccounts := map[string]uint32{ - waddrmgr.DefaultAccountName: waddrmgr.DefaultAccountNum, + waddrmgr.TstDefaultAccountName: waddrmgr.DefaultAccountNum, waddrmgr.ImportedAddrAccountName: waddrmgr.ImportedAddrAccount, } for acctName, expectedAccount := range expectedAccounts { @@ -1264,13 +1264,6 @@ func testRenameAccount(tc *testContext) bool { if !checkManagerError(tc.t, testName, err, wantErrCode) { return false } - // Test account name validation - testName = "*" - err = tc.manager.RenameAccount(tc.account, testName) - wantErrCode = waddrmgr.ErrInvalidAccount - if !checkManagerError(tc.t, testName, err, wantErrCode) { - return false - } return true } diff --git a/wallet/wallet.go b/wallet/wallet.go index 26dfd97..89ddf0f 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1127,6 +1127,11 @@ func (w *Wallet) ListUnspent(minconf, maxconf int32, } sort.Sort(sort.Reverse(creditSlice(unspent))) + defaultAccountName, err := w.Manager.AccountName(waddrmgr.DefaultAccountNum) + if err != nil { + return nil, err + } + results := make([]*btcjson.ListUnspentResult, 0, len(unspent)) for i := range unspent { output := &unspent[i] @@ -1157,7 +1162,7 @@ func (w *Wallet) ListUnspent(minconf, maxconf int32, // // This will be unnecessary once transactions and outputs are // grouped under the associated account in the db. - acctName := waddrmgr.DefaultAccountName + acctName := defaultAccountName _, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, w.chainParams) if err != nil {