Files
p9/cmd/wallet/methods.go
Loki Verloren 0e2bba237a initial commit
2021-05-03 10:43:10 +02:00

2384 lines
76 KiB
Go

package wallet
import (
"bytes"
"encoding/base64"
"encoding/hex"
js "encoding/json"
"errors"
"fmt"
"sync"
"time"
"github.com/p9c/p9/pkg/amt"
"github.com/p9c/p9/pkg/btcaddr"
"github.com/p9c/p9/pkg/chaincfg"
"github.com/p9c/p9/pkg/btcjson"
"github.com/p9c/p9/pkg/chainclient"
"github.com/p9c/p9/pkg/chainhash"
"github.com/p9c/p9/pkg/ecc"
"github.com/p9c/p9/pkg/interrupt"
"github.com/p9c/p9/pkg/rpcclient"
"github.com/p9c/p9/pkg/txrules"
"github.com/p9c/p9/pkg/txscript"
"github.com/p9c/p9/pkg/util"
"github.com/p9c/p9/pkg/waddrmgr"
"github.com/p9c/p9/pkg/wire"
"github.com/p9c/p9/pkg/wtxmgr"
)
// // 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 {
// return confirms(txHeight, curHeight) >= minconf
// }
// Confirms returns the number of confirmations for a transaction in a block at height txHeight (or -1 for an
// unconfirmed tx) given the chain height curHeight.
func Confirms(txHeight, curHeight int32) int32 {
switch {
case txHeight == -1, txHeight > curHeight:
return 0
default:
return curHeight - txHeight + 1
}
}
// var RPCHandlers = map[string]struct {
// Handler RequestHandler
// HandlerWithChain RequestHandlerChainRequired
// // 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.
// //
// // The Return field returns a new channel of the type returned by this function. This makes it possible to
// // use this for callers to receive a response in the `cpc` library which implements the functions as channel pipes
// NoHelp bool
// Params interface{}
// Return func() interface{}
// }{
// // Reference implementation wallet methods (implemented)
// "addmultisigaddress": {
// Handler: AddMultiSigAddress,
// Params: make(chan btcjson.AddMultisigAddressCmd),
// Return: func() interface{} { return make(chan AddMultiSigAddressRes) },
// },
// "createmultisig": {
// Handler: CreateMultiSig,
// Params: make(chan btcjson.CreateMultisigCmd),
// Return: func() interface{} { return make(chan CreateMultiSigRes) },
// },
// "dumpprivkey": {
// Handler: DumpPrivKey,
// Params: make(chan btcjson.DumpPrivKeyCmd),
// Return: func() interface{} { return make(chan DumpPrivKeyRes) },
// },
// "getaccount": {
// Handler: GetAccount,
// Params: make(chan btcjson.GetAccountCmd),
// Return: func() interface{} { return make(chan GetAccountRes) },
// },
// "getaccountaddress": {
// Handler: GetAccountAddress,
// Params: make(chan btcjson.GetAccountAddressCmd),
// Return: func() interface{} { return make(chan GetAccountAddressRes) },
// },
// "getaddressesbyaccount": {
// Handler: GetAddressesByAccount,
// Params: make(chan btcjson.GetAddressesByAccountCmd),
// Return: func() interface{} { return make(chan GetAddressesByAccountRes) },
// },
// "getbalance": {
// Handler: GetBalance,
// Params: make(chan btcjson.GetBalanceCmd),
// Return: func() interface{} { return make(chan GetBalanceRes) },
// },
// "getbestblockhash": {
// Handler: GetBestBlockHash,
// Return: func() interface{} { return make(chan GetBestBlockHashRes) },
// },
// "getblockcount": {
// Handler: GetBlockCount,
// Return: func() interface{} { return make(chan GetBlockCountRes) },
// },
// "getinfo": {
// HandlerWithChain: GetInfo,
// Return: func() interface{} { return make(chan GetInfoRes) },
// },
// "getnewaddress": {
// Handler: GetNewAddress,
// Params: make(chan btcjson.GetNewAddressCmd),
// Return: func() interface{} { return make(chan GetNewAddressRes) },
// },
// "getrawchangeaddress": {
// Handler: GetRawChangeAddress,
// Params: make(chan btcjson.GetRawChangeAddressCmd),
// Return: func() interface{} { return make(chan GetRawChangeAddressRes) },
// },
// "getreceivedbyaccount": {
// Handler: GetReceivedByAccount,
// Params: make(chan btcjson.GetReceivedByAccountCmd),
// Return: func() interface{} { return make(chan GetReceivedByAccountRes) },
// },
// "getreceivedbyaddress": {
// Handler: GetReceivedByAddress,
// Params: make(chan btcjson.GetReceivedByAddressCmd),
// Return: func() interface{} { return make(chan GetReceivedByAddressRes) },
// },
// "gettransaction": {
// Handler: GetTransaction,
// Params: make(chan btcjson.GetTransactionCmd),
// Return: func() interface{} { return make(chan GetTransactionRes) },
// },
// "help": {
// Handler: HelpNoChainRPC,
// HandlerWithChain: HelpWithChainRPC,
// Params: make(chan btcjson.HelpCmd),
// Return: func() interface{} { return make(chan HelpNoChainRPCRes) },
// },
// "importprivkey": {
// Handler: ImportPrivKey,
// Params: make(chan btcjson.ImportPrivKeyCmd),
// Return: func() interface{} { return make(chan ImportPrivKeyRes) },
// },
// "keypoolrefill": {
// Handler: KeypoolRefill,
// Params: qu.T(),
// Return: func() interface{} { return make(chan KeypoolRefillRes) },
// },
// "listaccounts": {
// Handler: ListAccounts,
// Params: make(chan btcjson.ListAccountsCmd),
// Return: func() interface{} { return make(chan ListAccountsRes) },
// },
// "listlockunspent": {
// Handler: ListLockUnspent,
// Params: qu.T(),
// Return: func() interface{} { return make(chan ListLockUnspentRes) },
// },
// "listreceivedbyaccount": {
// Handler: ListReceivedByAccount,
// Params: make(chan btcjson.ListReceivedByAccountCmd),
// Return: func() interface{} { return make(chan ListReceivedByAccountRes) },
// },
// "listreceivedbyaddress": {
// Handler: ListReceivedByAddress,
// Params: make(chan btcjson.ListReceivedByAddressCmd),
// Return: func() interface{} { return make(chan ListReceivedByAddressRes) },
// },
// "listsinceblock": {
// HandlerWithChain: ListSinceBlock,
// Params: make(chan btcjson.ListSinceBlockCmd),
// Return: func() interface{} { return make(chan ListSinceBlockRes) },
// },
// "listtransactions": {
// Handler: ListTransactions,
// Params: make(chan btcjson.ListTransactionsCmd),
// Return: func() interface{} { return make(chan ListTransactionsRes) },
// },
// "listunspent": {
// Handler: ListUnspent,
// Params: make(chan btcjson.ListUnspentCmd),
// Return: func() interface{} { return make(chan ListUnspentRes) },
// },
// "lockunspent": {
// Handler: LockUnspent,
// Params: make(chan btcjson.LockUnspentCmd),
// Return: func() interface{} { return make(chan LockUnspentRes) },
// },
// "sendfrom": {
// HandlerWithChain: SendFrom,
// Params: make(chan btcjson.SendFromCmd),
// Return: func() interface{} { return make(chan SendFromRes) },
// },
// "sendmany": {
// Handler: SendMany,
// Params: make(chan btcjson.SendManyCmd),
// Return: func() interface{} { return make(chan SendManyRes) },
// },
// "sendtoaddress": {
// Handler: SendToAddress,
// Params: make(chan btcjson.SendToAddressCmd),
// Return: func() interface{} { return make(chan SendToAddressRes) },
// },
// "settxfee": {
// Handler: SetTxFee,
// Params: make(chan btcjson.SetTxFeeCmd),
// Return: func() interface{} { return make(chan SetTxFeeRes) },
// },
// "signmessage": {
// Handler: SignMessage,
// Params: make(chan btcjson.SignMessageCmd),
// Return: func() interface{} { return make(chan SignMessageRes) },
// },
// "signrawtransaction": {
// HandlerWithChain: SignRawTransaction,
// Params: make(chan btcjson.SignRawTransactionCmd),
// Return: func() interface{} { return make(chan SignRawTransactionRes) },
// },
// "validateaddress": {
// Handler: ValidateAddress,
// Params: make(chan btcjson.ValidateAddressCmd),
// Return: func() interface{} { return make(chan ValidateAddressRes) },
// },
// "verifymessage": {
// Handler: VerifyMessage,
// Params: make(chan btcjson.VerifyMessageCmd),
// Return: func() interface{} { return make(chan VerifyMessageRes) },
// },
// "walletlock": {
// Handler: WalletLock,
// Params: qu.T(),
// Return: func() interface{} { return make(chan WalletLockRes) },
// },
// "walletpassphrase": {
// Handler: WalletPassphrase,
// Params: make(chan btcjson.WalletPassphraseCmd),
// Return: func() interface{} { return make(chan WalletPassphraseRes) },
// },
// "walletpassphrasechange": {
// Handler: WalletPassphraseChange,
// Params: make(chan btcjson.WalletPassphraseChangeCmd),
// Return: func() interface{} { return make(chan WalletPassphraseChangeRes) },
// },
// // Reference implementation methods (still 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": {Handler: Unsupported, NoHelp: true},
// "move": {Handler: Unsupported, NoHelp: true},
// "setaccount": {Handler: Unsupported, NoHelp: true},
// // Extensions to the reference client JSON-RPC API
// "createnewaccount": {
// Handler: CreateNewAccount,
// Params: make(chan btcjson.CreateNewAccountCmd),
// Return: func() interface{} { return make(chan CreateNewAccountRes) },
// },
// "getbestblock": {
// Handler: GetBestBlock,
// Params: qu.T(),
// Return: func() interface{} { return make(chan GetBestBlockRes) },
// },
// // 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": {
// Handler: GetUnconfirmedBalance,
// Params: make(chan btcjson.GetUnconfirmedBalanceCmd),
// Return: func() interface{} { return make(chan GetUnconfirmedBalanceRes) },
// },
// "listaddresstransactions": {
// Handler: ListAddressTransactions,
// Params: make(chan btcjson.ListAddressTransactionsCmd),
// Return: func() interface{} { return make(chan ListAddressTransactionsRes) },
// },
// "listalltransactions": {
// Handler: ListAllTransactions,
// Params: make(chan btcjson.ListAllTransactionsCmd),
// Return: func() interface{} { return make(chan ListAllTransactionsRes) },
// },
// "renameaccount": {
// Handler: RenameAccount,
// Params: make(chan btcjson.RenameAccountCmd),
// Return: func() interface{} { return make(chan RenameAccountRes) },
// },
// "walletislocked": {
// Handler: WalletIsLocked,
// Params: qu.T(),
// Return: func() interface{} { return make(chan WalletIsLockedRes) },
// },
// "dropwallethistory": {
// Handler: HandleDropWalletHistory,
// Params: qu.T(),
// Return: func() interface{} { return make(chan DropWalletHistoryRes) },
// },
// }
// Unimplemented handles an Unimplemented RPC request with the
// appropiate error.
func Unimplemented(interface{}, *Wallet) (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(interface{}, *Wallet) (interface{}, error) {
return nil, &btcjson.RPCError{
Code: -1,
Message: "Request unsupported by wallet",
}
}
// LazyHandler 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 LazyHandler func() (interface{}, *btcjson.RPCError)
// LazyApplyHandler looks up the best request handler func for the method, returning a closure that will execute it with
// the (required) wallet and (optional) consensus RPC server. If no handlers are found and the chainClient is not nil,
// the returned handler performs RPC passthrough.
func LazyApplyHandler(request *btcjson.Request, w *Wallet, chainClient chainclient.Interface) LazyHandler {
handlerData, ok := RPCHandlers[request.Method]
D.Ln("LazyApplyHandler >>> >>> >>>", ok, handlerData.Handler != nil, w != nil, chainClient != nil)
if ok && handlerData.Handler != nil && w != nil && chainClient != nil {
D.Ln("found handler for call")
// D.S(request)
return func() (interface{}, *btcjson.RPCError) {
cmd, e := btcjson.UnmarshalCmd(request)
if e != nil {
return nil, btcjson.ErrRPCInvalidRequest
}
switch client := chainClient.(type) {
case *chainclient.RPCClient:
D.Ln("client is a chain.RPCClient")
var resp interface{}
if resp, e = handlerData.Handler(cmd, w, client); E.Chk(e) {
return nil, JSONError(e)
}
D.Ln("handler call succeeded")
return resp, nil
default:
D.Ln("client is unknown")
return nil, &btcjson.RPCError{
Code: -1,
Message: "Chain RPC is inactive",
}
}
}
}
D.Ln("failed to find handler for call")
// I.Ln("handler", handlerData.Handler, "wallet", w)
if ok && handlerData.Handler != nil && w != nil {
D.Ln("handling", request.Method)
return func() (interface{}, *btcjson.RPCError) {
cmd, e := btcjson.UnmarshalCmd(request)
if e != nil {
return nil, btcjson.ErrRPCInvalidRequest
}
var resp interface{}
if resp, e = handlerData.Handler(cmd, w); E.Chk(e) {
return nil, JSONError(e)
}
return resp, nil
}
}
// Fallback to RPC passthrough
return func() (interface{}, *btcjson.RPCError) {
I.Ln("passing to node", request.Method)
if chainClient == nil {
return nil, &btcjson.RPCError{
Code: -1,
Message: "Chain RPC is inactive",
}
}
switch client := chainClient.(type) {
case *chainclient.RPCClient:
resp, e := client.RawRequest(
request.Method,
request.Params,
)
if e != nil {
return nil, JSONError(e)
}
return &resp, nil
default:
return nil, &btcjson.RPCError{
Code: -1,
Message: "Chain RPC is inactive",
}
}
}
}
// 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{}, e error) btcjson.Response {
idPtr := IDPointer(id)
if e != nil {
return btcjson.Response{
ID: idPtr,
Error: JSONError(e),
}
}
var resultBytes []byte
resultBytes, e = js.Marshal(result)
if e != nil {
return btcjson.Response{
ID: idPtr,
Error: &btcjson.RPCError{
Code: btcjson.ErrRPCInternal.Code,
Message: "Unexpected error marshalling result",
},
}
}
return btcjson.Response{
ID: idPtr,
Result: resultBytes,
}
}
// JSONError creates a JSON-RPC error from the Go error.
func JSONError(e error) *btcjson.RPCError {
if e == nil {
return nil
}
code := btcjson.ErrRPCWallet
switch e := e.(type) {
case btcjson.RPCError:
return &e
case *btcjson.RPCError:
return e
case DeserializationError:
code = btcjson.ErrRPCDeserialization
case InvalidParameterError:
code = btcjson.ErrRPCInvalidParameter
case ParseError:
code = btcjson.ErrRPCParse.Code
case waddrmgr.ManagerError:
switch e.ErrorCode {
case waddrmgr.ErrWrongPassphrase:
code = btcjson.ErrRPCWalletPassphraseIncorrect
}
}
return &btcjson.RPCError{
Code: code,
Message: e.Error(),
}
}
// MakeMultiSigScript is a helper function to combine common logic for AddMultiSig and CreateMultiSig.
func MakeMultiSigScript(w *Wallet, keys []string, nRequired int) ([]byte, error) {
keysesPrecious := make([]*btcaddr.PubKey, len(keys))
// The address list will made up either of addresses (pubkey hash), for which we need to look up the keys in
// wallet, straight pubkeys, or a mixture of the two.
for i, a := range keys {
// try to parse as pubkey address
a, e := DecodeAddress(a, w.ChainParams())
if e != nil {
return nil, e
}
switch addr := a.(type) {
case *btcaddr.PubKey:
keysesPrecious[i] = addr
default:
pubKey, e := w.PubKeyForAddress(addr)
if e != nil {
return nil, e
}
pubKeyAddr, e := btcaddr.NewPubKey(
pubKey.SerializeCompressed(), w.ChainParams(),
)
if e != nil {
return nil, e
}
keysesPrecious[i] = pubKeyAddr
}
}
return txscript.MultiSigScript(keysesPrecious, nRequired)
}
// AddMultiSigAddress handles an addmultisigaddress request by adding a
// multisig address to the given wallet.
func AddMultiSigAddress(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (
interface{},
error,
) {
var msg string
cmd, ok := icmd.(*btcjson.AddMultisigAddressCmd)
// cmd, ok := icmd.(*btcjson.ListTransactionsCmd)
if !ok {
var h string
h = HelpDescsEnUS()["addmultisigaddress"]
msg += h
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: msg,
// "invalid subcommand for addnode",
}
}
// If an account is specified, ensure that is the imported account.
if cmd.Account != nil && *cmd.Account != waddrmgr.ImportedAddrAccountName {
return nil, &ErrNotImportedAccount
}
secp256k1Addrs := make([]btcaddr.Address, len(cmd.Keys))
for i, k := range cmd.Keys {
addr, e := DecodeAddress(k, w.ChainParams())
if e != nil {
return nil, ParseError{e}
}
secp256k1Addrs[i] = addr
}
script, e := w.MakeMultiSigScript(secp256k1Addrs, cmd.NRequired)
if e != nil {
return nil, e
}
p2shAddr, e := w.ImportP2SHRedeemScript(script)
if e != nil {
return nil, e
}
return p2shAddr.EncodeAddress(), nil
}
// CreateMultiSig handles an createmultisig request by returning a multisig address for the given inputs.
func CreateMultiSig(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
var msg string
cmd, ok := icmd.(*btcjson.CreateMultisigCmd)
if !ok {
var h string
h = HelpDescsEnUS()["createmultisig"]
msg += h
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: msg,
// "invalid subcommand for addnode",
}
}
script, e := MakeMultiSigScript(w, cmd.Keys, cmd.NRequired)
if e != nil {
return nil, ParseError{e}
}
address, e := btcaddr.NewScriptHash(script, w.ChainParams())
if e != nil {
// above is a valid script, shouldn't happen.
return nil, e
}
return btcjson.CreateMultiSigResult{
Address: address.EncodeAddress(),
RedeemScript: hex.EncodeToString(script),
}, nil
}
// DumpPrivKey handles a dumpprivkey request with the private key for a single address, or an appropriate error if the
// wallet is locked.
func DumpPrivKey(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
var msg string
cmd, ok := icmd.(*btcjson.DumpPrivKeyCmd)
if !ok {
msg = HelpDescsEnUS()["dumpprivkey"]
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: msg,
// "invalid subcommand for addnode",
}
}
addr, e := DecodeAddress(cmd.Address, w.ChainParams())
if e != nil {
return nil, e
}
key, e := w.DumpWIFPrivateKey(addr)
if waddrmgr.IsError(e, waddrmgr.ErrLocked) {
// Address was found, but the private key isn't accessible.
return nil, &ErrWalletUnlockNeeded
}
return key, e
}
// // 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(// icmd interface{}, w *wallet.Wallet) (interface{}, error) {
// keys, e := w.DumpPrivKeys()
// if waddrmgr.IsError(err, waddrmgr.ErrLocked) {
// return nil, &ErrWalletUnlockNeeded
// }
// return keys, err
// }
// GetAddressesByAccount handles a getaddressesbyaccount request by returning
// all addresses for an account, or an error if the requested account does not
// exist.
func GetAddressesByAccount(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (
interface{},
error,
) {
cmd, ok := icmd.(*btcjson.GetAddressesByAccountCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["dumpprivkey"],
// "invalid subcommand for addnode",
}
}
account, e := w.AccountNumber(waddrmgr.KeyScopeBIP0044, cmd.Account)
if e != nil {
return nil, e
}
addrs, e := w.AccountAddresses(account)
if e != nil {
return nil, e
}
addrStrs := make([]string, len(addrs))
for i, a := range addrs {
addrStrs[i] = a.EncodeAddress()
}
return addrStrs, nil
}
// 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(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
cmd, ok := icmd.(*btcjson.GetBalanceCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["getbalance"],
// "invalid subcommand for addnode",
}
}
var balance amt.Amount
var e error
accountName := "*"
if cmd.Account != nil {
accountName = *cmd.Account
}
if accountName == "*" {
balance, e = w.CalculateBalance(int32(*cmd.MinConf))
if e != nil {
return nil, e
}
} else {
var account uint32
account, e = w.AccountNumber(waddrmgr.KeyScopeBIP0044, accountName)
if e != nil {
return nil, e
}
bals, e := w.CalculateAccountBalances(account, int32(*cmd.MinConf))
if e != nil {
return nil, e
}
balance = bals.Spendable
}
return balance.ToDUO(), nil
}
// GetBestBlock handles a getbestblock request by returning a JSON object with
// the height and hash of the most recently processed block.
func GetBestBlock(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
blk := w.Manager.SyncedTo()
result := &btcjson.GetBestBlockResult{
Hash: blk.Hash.String(),
Height: blk.Height,
}
return result, nil
}
// GetBestBlockHash handles a getbestblockhash request by returning the hash of
// the most recently processed block.
func GetBestBlockHash(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (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(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
blk := w.Manager.SyncedTo()
return blk.Height, nil
}
// GetInfo handles a getinfo request by returning the a structure containing
// information about the current state of btcwallet. exist.
func GetInfo(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
if len(chainClient) < 1 || chainClient[0] == nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCNoChain,
Message: "there is currently no chain client to get this response",
}
}
// Call down to pod for all of the information in this command known by them.
var info *btcjson.InfoWalletResult
var e error
info, e = chainClient[0].GetInfo()
if e != nil {
return nil, e
}
var bal amt.Amount
bal, e = w.CalculateBalance(1)
if e != nil {
return nil, e
}
// TODO(davec): This should probably have a database version as opposed
// to using the manager version.
info.WalletVersion = int32(waddrmgr.LatestMgrVersion)
info.Balance = bal.ToDUO()
info.PaytxFee = float64(txrules.DefaultRelayFeePerKb)
// We don't set the following since they don't make much sense in the wallet architecture:
//
// - unlocked_until
// - errors
return info, nil
}
func DecodeAddress(s string, params *chaincfg.Params) (btcaddr.Address, error) {
addr, e := btcaddr.Decode(s, params)
if e != nil {
msg := fmt.Sprintf("Invalid address %q: decode failed with %#q", s, e)
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidAddressOrKey,
Message: msg,
}
}
if !addr.IsForNet(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(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.GetAccountCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["getaccount"],
// "invalid subcommand for addnode",
}
}
addr, e := DecodeAddress(cmd.Address, w.ChainParams())
if e != nil {
return nil, e
}
// Fetch the associated account
account, e := w.AccountOfAddress(addr)
if e != nil {
return nil, &ErrAddressNotInWallet
}
acctName, e := w.AccountName(waddrmgr.KeyScopeBIP0044, account)
if e != nil {
return nil, &ErrAccountNameNotFound
}
return acctName, nil
}
// GetAccountAddress handles a getaccountaddress by returning the most
// recently-created chained address that has not yet been used (does not yet
// appear in the blockchain, or any tx that has arrived in the pod 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 json.ErrRPCWalletKeypoolRanOut if that happens).
func GetAccountAddress(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
cmd, ok := icmd.(*btcjson.GetAccountAddressCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["getaccountaddress"],
// "invalid subcommand for addnode",
}
}
account, e := w.AccountNumber(waddrmgr.KeyScopeBIP0044, cmd.Account)
if e != nil {
return nil, e
}
addr, e := w.CurrentAddress(account, waddrmgr.KeyScopeBIP0044)
if e != nil {
return nil, e
}
return addr.EncodeAddress(), e
}
// GetUnconfirmedBalance handles a getunconfirmedbalance extension request by
// returning the current unconfirmed balance of an account.
func GetUnconfirmedBalance(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (
interface{},
error,
) {
cmd, ok := icmd.(*btcjson.GetUnconfirmedBalanceCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["getunconfirmedbalance"],
// "invalid subcommand for addnode",
}
}
acctName := "default"
if cmd.Account != nil {
acctName = *cmd.Account
}
account, e := w.AccountNumber(waddrmgr.KeyScopeBIP0044, acctName)
if e != nil {
return nil, e
}
bals, e := w.CalculateAccountBalances(account, 1)
if e != nil {
return nil, e
}
return (bals.Total - bals.Spendable).ToDUO(), nil
}
// ImportPrivKey handles an importprivkey request by parsing a WIF-encoded
// private key and adding it to an account.
func ImportPrivKey(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
cmd, ok := icmd.(*btcjson.ImportPrivKeyCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["importprivkey"],
// "invalid subcommand for addnode",
}
}
// Ensure that private keys are only imported to the correct account.
//
// Yes, Label is the account name.
if cmd.Label != nil && *cmd.Label != waddrmgr.ImportedAddrAccountName {
return nil, &ErrNotImportedAccount
}
wif, e := util.DecodeWIF(cmd.PrivKey)
if e != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidAddressOrKey,
Message: "WIF decode failed: " + e.Error(),
}
}
if !wif.IsForNet(w.ChainParams()) {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidAddressOrKey,
Message: "Key is not intended for " + w.ChainParams().Name,
}
}
// Import the private key, handling any errors.
_, e = w.ImportPrivateKey(waddrmgr.KeyScopeBIP0044, wif, nil, *cmd.Rescan)
switch {
case waddrmgr.IsError(e, waddrmgr.ErrDuplicateAddress):
// Do not return duplicate key errors to the client.
return nil, nil
case waddrmgr.IsError(e, waddrmgr.ErrLocked):
return nil, &ErrWalletUnlockNeeded
}
return nil, e
}
// KeypoolRefill handles the keypoolrefill command. Since we handle the keypool automatically this does nothing since
// refilling is never manually required.
func KeypoolRefill(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (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(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
cmd, ok := icmd.(*btcjson.CreateNewAccountCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["createnewaccount"],
// "invalid subcommand for addnode",
}
}
// 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
}
_, e := w.NextAccount(waddrmgr.KeyScopeBIP0044, cmd.Account)
if waddrmgr.IsError(e, waddrmgr.ErrLocked) {
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",
}
}
return nil, e
}
// RenameAccount handles a renameaccount request by renaming an account. If the
// account does not exist an appropiate error will be returned.
func RenameAccount(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
cmd, ok := icmd.(*btcjson.RenameAccountCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["renameaccount"],
// "invalid subcommand for addnode",
}
}
// 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
}
// Chk that given account exists
account, e := w.AccountNumber(waddrmgr.KeyScopeBIP0044, cmd.OldAccount)
if e != nil {
return nil, e
}
return nil, w.RenameAccount(waddrmgr.KeyScopeBIP0044, account, cmd.NewAccount)
}
// GetNewAddress handles a getnewaddress request by returning a new address for
// an account. If the account does not exist an appropiate error is returned.
//
// TODO: Follow BIP 0044 and warn if number of unused addresses exceeds the gap limit.
func GetNewAddress(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
cmd, ok := icmd.(*btcjson.GetNewAddressCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["getnewaddress"],
// "invalid subcommand for addnode",
}
}
acctName := "default"
if cmd.Account != nil {
acctName = *cmd.Account
}
account, e := w.AccountNumber(waddrmgr.KeyScopeBIP0044, acctName)
if e != nil {
return nil, e
}
addr, e := w.NewAddress(account, waddrmgr.KeyScopeBIP0044, false)
if e != nil {
return nil, e
}
// Return the new payment address string.
return addr.EncodeAddress(), nil
}
// GetRawChangeAddress handles a getrawchangeaddress request by creating and
// returning a new change address for an account.
//
// Note: bitcoind allows specifying the account as an optional parameter, but
// ignores the parameter.
func GetRawChangeAddress(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (
interface{},
error,
) {
cmd, ok := icmd.(*btcjson.GetRawChangeAddressCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["getrawchangeaddress"],
// "invalid subcommand for addnode",
}
}
acctName := "default"
if cmd.Account != nil {
acctName = *cmd.Account
}
account, e := w.AccountNumber(waddrmgr.KeyScopeBIP0044, acctName)
if e != nil {
return nil, e
}
addr, e := w.NewChangeAddress(account, waddrmgr.KeyScopeBIP0044)
if e != nil {
return nil, e
}
// Return the new payment address string.
return addr.EncodeAddress(), nil
}
// GetReceivedByAccount handles a getreceivedbyaccount request by returning the
// total amount received by addresses of an account.
func GetReceivedByAccount(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (
ii interface{},
e error,
) {
cmd, ok := icmd.(*btcjson.GetReceivedByAccountCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["getreceivedbyaccount"],
// "invalid subcommand for addnode",
}
}
var account uint32
account, e = w.AccountNumber(waddrmgr.KeyScopeBIP0044, cmd.Account)
if e != nil {
return nil, e
}
// TODO: This is more inefficient that it could be, but the entire algorithm is
// already dominated by reading every transaction in the wallet's history.
var results []AccountTotalReceivedResult
results, e = w.TotalReceivedForAccounts(
waddrmgr.KeyScopeBIP0044, int32(*cmd.MinConf),
)
if e != nil {
return nil, e
}
acctIndex := int(account)
if account == waddrmgr.ImportedAddrAccount {
acctIndex = len(results) - 1
}
return results[acctIndex].TotalReceived.ToDUO(), nil
}
// GetReceivedByAddress handles a getreceivedbyaddress request by returning the total amount received by a single
// address.
func GetReceivedByAddress(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (
interface{},
error,
) {
cmd, ok := icmd.(*btcjson.GetReceivedByAddressCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["getreceivedbyaddress"],
// "invalid subcommand for addnode",
}
}
addr, e := DecodeAddress(cmd.Address, w.ChainParams())
if e != nil {
return nil, e
}
total, e := w.TotalReceivedForAddr(addr, int32(*cmd.MinConf))
if e != nil {
return nil, e
}
return total.ToDUO(), nil
}
// GetTransaction handles a gettransaction request by returning details about a single transaction saved by wallet.
func GetTransaction(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
cmd, ok := icmd.(*btcjson.GetTransactionCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["gettransaction"],
// "invalid subcommand for addnode",
}
}
txHash, e := chainhash.NewHashFromStr(cmd.Txid)
if e != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCDecodeHexString,
Message: "Transaction hash string decode failed: " + e.Error(),
}
}
details, e := ExposeUnstableAPI(w).TxDetails(txHash)
if e != nil {
return nil, e
}
if details == nil {
return nil, &ErrNoTransactionInfo
}
syncBlock := w.Manager.SyncedTo()
// TODO: The serialized transaction is already in the DB, so
// reserializing can be avoided here.
var txBuf bytes.Buffer
txBuf.Grow(details.MsgTx.SerializeSize())
e = details.MsgTx.Serialize(&txBuf)
if e != nil {
return nil, e
}
// TODO: Add a "generated" field to this result type. "generated":true
// is only added if the transaction is a coinbase.
ret := btcjson.GetTransactionResult{
TxID: cmd.Txid,
Hex: hex.EncodeToString(txBuf.Bytes()),
Time: details.Received.Unix(),
TimeReceived: details.Received.Unix(),
WalletConflicts: []string{}, // Not saved
// Generated: blockchain.IsCoinBaseTx(&details.MsgTx),
}
if details.Block.Height != -1 {
ret.BlockHash = details.Block.Hash.String()
ret.BlockTime = details.Block.Time.Unix()
ret.Confirmations = int64(Confirms(details.Block.Height, syncBlock.Height))
}
var (
debitTotal amt.Amount
creditTotal amt.Amount // Excludes change
fee amt.Amount
feeF64 float64
)
for _, deb := range details.Debits {
debitTotal += deb.Amount
}
for _, cred := range details.Credits {
if !cred.Change {
creditTotal += cred.Amount
}
}
// Fee can only be determined if every input is a debit.
if len(details.Debits) == len(details.MsgTx.TxIn) {
var outputTotal amt.Amount
for _, output := range details.MsgTx.TxOut {
outputTotal += amt.Amount(output.Value)
}
fee = debitTotal - outputTotal
feeF64 = fee.ToDUO()
}
if len(details.Debits) == 0 {
// Credits must be set later, but since we know the full length
// of the details slice, allocate it with the correct cap.
ret.Details = make([]btcjson.GetTransactionDetailsResult, 0, len(details.Credits))
} else {
ret.Details = make([]btcjson.GetTransactionDetailsResult, 1, len(details.Credits)+1)
ret.Details[0] = btcjson.GetTransactionDetailsResult{
// Fields left zeroed:
// InvolvesWatchOnly
// Account
// Address
// VOut
//
// TODO(jrick): Address and VOut should always be set,
// but we're doing the wrong thing here by not matching
// core. Instead, gettransaction should only be adding
// details for transaction outputs, just like
// listtransactions (but using the short result format).
Category: "send",
Amount: (-debitTotal).ToDUO(), // negative since it is a send
Fee: &feeF64,
}
ret.Fee = feeF64
}
credCat := RecvCategory(details, syncBlock.Height, w.ChainParams()).String()
for _, cred := range details.Credits {
// Change is ignored.
if cred.Change {
continue
}
var address string
var accountName string
var addrs []btcaddr.Address
_, addrs, _, e = txscript.ExtractPkScriptAddrs(
details.MsgTx.TxOut[cred.Index].PkScript, w.ChainParams(),
)
if e == nil && len(addrs) == 1 {
addr := addrs[0]
address = addr.EncodeAddress()
account, e := w.AccountOfAddress(addr)
if e == nil {
name, e := w.AccountName(waddrmgr.KeyScopeBIP0044, account)
if e == nil {
accountName = name
}
}
}
ret.Details = append(
ret.Details, btcjson.GetTransactionDetailsResult{
// Fields left zeroed:
// InvolvesWatchOnly
// Fee
Account: accountName,
Address: address,
Category: credCat,
Amount: cred.Amount.ToDUO(),
Vout: cred.Index,
},
)
}
ret.Amount = creditTotal.ToDUO()
return ret, nil
}
func HandleDropWalletHistory(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (
out interface{}, e error,
) {
D.Ln("dropping wallet history")
if e = DropWalletHistory(w, w.PodConfig); E.Chk(e) {
}
D.Ln("dropped wallet history")
// go func() {
// rwt, e := w.Database().BeginReadWriteTx()
// if e != nil {
// L.Script // }
// ns := rwt.ReadWriteBucket([]byte("waddrmgr"))
// w.Manager.SetSyncedTo(ns, nil)
// if e = rwt.Commit(); E.Chk(e) {
// }
// }()
defer interrupt.RequestRestart()
return nil, e
}
// 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.
//
var HelpDescs map[string]string
var HelpDescsMutex sync.Mutex // Help may execute concurrently, so synchronize access.
// HelpWithChainRPC handles the help request when the RPC server has been associated with a consensus RPC client. The
// additional RPC client is used to include help messages for methods implemented by the consensus server via RPC
// passthrough.
func HelpWithChainRPC(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
return Help(icmd, w, chainClient[0])
}
// HelpNoChainRPC handles the help request when the RPC server has not been associated with a consensus RPC client. No
// help messages are included for passthrough requests.
func HelpNoChainRPC(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
return Help(icmd, w, nil)
}
// Help handles the Help request by returning one line usage of all available methods, or full Help for a specific
// method. The chainClient is optional, and this is simply a helper function for the HelpNoChainRPC and HelpWithChainRPC
// handlers.
func Help(icmd interface{}, w *Wallet, chainClient *chainclient.RPCClient) (interface{}, error) {
cmd := icmd.(*btcjson.HelpCmd)
// pod 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 pod. Therefore, create a POST client as needed.
//
// Returns nil if chainClient 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 pod package.
postClient := func() *rpcclient.Client {
if chainClient == nil {
return nil
}
c, e := chainClient.POSTClient()
if e != nil {
return nil
}
return c
}
if cmd.Command == nil || *cmd.Command == "" {
// Prepend chain server usage if it is available.
usages := RequestUsages
client := postClient()
if client != nil {
rawChainUsage, e := client.RawRequest("help", nil)
var chainUsage string
if e == nil {
_ = js.Unmarshal(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 HelpDescsMutex.Unlock()
HelpDescsMutex.Lock()
if HelpDescs == nil {
// TODO: Allow other locales to be set via config or determine 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, e := client.RawRequest("help", []js.RawMessage{param})
if e == nil {
_ = js.Unmarshal(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(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.ListAccountsCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["listaccounts"],
// "invalid subcommand for addnode",
}
}
accountBalances := map[string]float64{}
results, e := w.AccountBalances(waddrmgr.KeyScopeBIP0044, int32(*cmd.MinConf))
if e != nil {
return nil, e
}
for _, result := range results {
accountBalances[result.AccountName] = result.AccountBalance.ToDUO()
}
// Return the map. This will be marshaled into a JSON object.
return accountBalances, nil
}
// ListLockUnspent handles a listlockunspent request by returning an slice of all locked outpoints.
func ListLockUnspent(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
return w.LockedOutpoints(), nil
}
// ListReceivedByAccount handles a listreceivedbyaccount request by returning a slice of objects, each one containing:
//
// "account": the receiving account;
//
// "amount": total amount received by the account;
//
// "confirmations": number of confirmations of the most recent transaction.
//
// It takes two parameters:
//
// "minconf": minimum number of confirmations to consider a transaction - default: one;
//
// "includeempty": whether or not to include addresses that have no transactions - default: false.
func ListReceivedByAccount(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.ListReceivedByAccountCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["listreceivedbyaccount"],
// "invalid subcommand for addnode",
}
}
results, e := w.TotalReceivedForAccounts(
waddrmgr.KeyScopeBIP0044, int32(*cmd.MinConf),
)
if e != nil {
return nil, e
}
jsonResults := make([]btcjson.ListReceivedByAccountResult, 0, len(results))
for _, result := range results {
jsonResults = append(
jsonResults, btcjson.ListReceivedByAccountResult{
Account: result.AccountName,
Amount: result.TotalReceived.ToDUO(),
Confirmations: uint64(result.LastConfirmation),
},
)
}
return jsonResults, nil
}
// ListReceivedByAddress handles a listreceivedbyaddress request by returning
// a slice of objects, each one containing:
//
// "account": the account of the receiving address;
//
// "address": the receiving address;
//
// "amount": total amount received by the address;
//
// "confirmations": number of confirmations of the most recent transaction.
//
// It takes two parameters:
//
// "minconf": minimum number of confirmations to consider a transaction - default: one;
//
// "includeempty": whether or not to include addresses that have no transactions - default: false.
func ListReceivedByAddress(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.ListReceivedByAddressCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["listreceivedbyaddress"],
// "invalid subcommand for addnode",
}
}
// Intermediate data for each address.
type AddrData struct {
// Total amount received.
amount amt.Amount
// Number of confirmations of the last transaction.
confirmations int32
// Merkles of transactions which include an output paying to the address
tx []string
// Account which the address belongs to account string
}
syncBlock := w.Manager.SyncedTo()
// Intermediate data for all addresses.
allAddrData := make(map[string]AddrData)
// Create an AddrData entry for each active address in the account. Otherwise we'll just get addresses from
// transactions later.
sortedAddrs, e := w.SortedActivePaymentAddresses()
if e != nil {
return nil, e
}
for _, address := range sortedAddrs {
// There might be duplicates, just overwrite them.
allAddrData[address] = AddrData{}
}
minConf := *cmd.MinConf
var endHeight int32
if minConf == 0 {
endHeight = -1
} else {
endHeight = syncBlock.Height - int32(minConf) + 1
}
e = ExposeUnstableAPI(w).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 {
pkScript := tx.MsgTx.TxOut[cred.Index].PkScript
var addrs []btcaddr.Address
_, addrs, _, e = txscript.ExtractPkScriptAddrs(
pkScript, w.ChainParams(),
)
if e != nil {
// Non standard script, skip.
continue
}
for _, addr := range addrs {
addrStr := addr.EncodeAddress()
addrData, ok := allAddrData[addrStr]
if ok {
addrData.amount += cred.Amount
// Always overwrite confirmations with newer ones.
addrData.confirmations = confirmations
} else {
addrData = AddrData{
amount: cred.Amount,
confirmations: confirmations,
}
}
addrData.tx = append(addrData.tx, tx.Hash.String())
allAddrData[addrStr] = addrData
}
}
}
return false, nil
},
)
if e != nil {
return nil, e
}
// Massage address data into output format.
numAddresses := len(allAddrData)
ret := make([]btcjson.ListReceivedByAddressResult, numAddresses)
idx := 0
for address, addrData := range allAddrData {
ret[idx] = btcjson.ListReceivedByAddressResult{
Address: address,
Amount: addrData.amount.ToDUO(),
Confirmations: uint64(addrData.confirmations),
TxIDs: addrData.tx,
}
idx++
}
return ret, nil
}
// 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(
icmd interface{}, w *Wallet,
cc ...*chainclient.RPCClient,
) (interface{}, error) {
if len(cc) < 1 || cc[0] == nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCNoChain,
Message: "there is currently no chain client to get this response",
}
}
chainClient := cc[0]
cmd, ok := icmd.(*btcjson.ListSinceBlockCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["listsinceblock"],
// "invalid subcommand for addnode",
}
}
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 := chainClient.GetBlockHashAsync(int64(syncBlock.Height) + 1 - targetConf)
var start int32
if cmd.BlockHash != nil {
hash, e := chainhash.NewHashFromStr(*cmd.BlockHash)
if e != nil {
return nil, DeserializationError{e}
}
block, e := chainClient.GetBlockVerboseTx(hash)
if e != nil {
return nil, e
}
start = int32(block.Height) + 1
}
txInfoList, e := w.ListSinceBlock(start, -1, syncBlock.Height)
if e != nil {
return nil, e
}
// Done with work, get the response.
blockHash, e := gbh.Receive()
if e != nil {
return nil, e
}
res := btcjson.ListSinceBlockResult{
Transactions: txInfoList,
LastBlock: blockHash.String(),
}
return res, nil
}
// ListTransactions handles a listtransactions request by returning an array of maps with details of sent and recevied
// wallet transactions.
func ListTransactions(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (
txs interface{},
e error,
) {
// D.S(icmd)
// D.Ln("ListTransactions")
if len(chainClient) < 1 || chainClient[0] == nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCNoChain,
Message: "there is currently no chain client to get this response",
}
}
cmd, ok := icmd.(*btcjson.ListTransactionsCmd)
if !ok { // || cmd.From == nil || cmd.Count == nil || cmd.Account != nil {
E.Ln(
"invalid parameter ok",
!ok,
"from",
cmd.From == nil,
"count",
cmd.Count == nil,
"account",
cmd.Account != nil,
)
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["listtransactions"],
}
}
// // 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 != "*" {
// // For now, don't bother trying to continue if the user specified an account, since this can't be (easily or
// // efficiently) calculated.
// E.Ln("you must use * for account, as transactions are not yet grouped by account")
// return nil, &btcjson.RPCError{
// Code: btcjson.ErrRPCWallet,
// Message: "Transactions are not yet grouped by account",
// }
// }
txs, e = w.ListTransactions(*cmd.From, *cmd.Count)
return txs, e
}
// ListAddressTransactions handles a listaddresstransactions request by returning an array of maps with details of spent
// and received wallet 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(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.ListAddressTransactionsCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["listaddresstransactions"],
// "invalid subcommand for addnode",
}
}
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 {
addr, e := DecodeAddress(addrStr, w.ChainParams())
if e != nil {
return nil, e
}
hash160Map[string(addr.ScriptAddress())] = struct{}{}
}
return w.ListAddressTransactions(hash160Map)
}
// ListAllTransactions handles a listalltransactions request by returning a map with details of sent and received 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(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.ListAllTransactionsCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["listalltransactions"],
// "invalid subcommand for addnode",
}
}
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()
}
// ListUnspent handles the listunspent command.
func ListUnspent(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.ListUnspentCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["listunspent"],
// "invalid subcommand for addnode",
}
}
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, e := DecodeAddress(as, w.ChainParams())
if e != nil {
return nil, e
}
addresses[a.EncodeAddress()] = struct{}{}
}
}
return w.ListUnspent(int32(*cmd.MinConf), int32(*cmd.MaxConf), addresses)
}
// LockUnspent handles the lockunspent command.
func LockUnspent(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.LockUnspentCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["lockunspent"],
// "invalid subcommand for addnode",
}
}
switch {
case cmd.Unlock && len(cmd.Transactions) == 0:
w.ResetLockedOutpoints()
default:
for _, input := range cmd.Transactions {
txHash, e := chainhash.NewHashFromStr(input.Txid)
if e != nil {
return nil, ParseError{e}
}
op := wire.OutPoint{Hash: *txHash, Index: input.Vout}
if cmd.Unlock {
w.UnlockOutpoint(op)
} else {
w.LockOutpoint(op)
}
}
}
return true, nil
}
// MakeOutputs creates a slice of transaction outputs from a pair of address strings to amounts. This is used to create
// the outputs to include in newly created transactions from a JSON object describing the output destinations and
// amounts.
func MakeOutputs(pairs map[string]amt.Amount, chainParams *chaincfg.Params) ([]*wire.TxOut, error) {
outputs := make([]*wire.TxOut, 0, len(pairs))
for addrStr, amt := range pairs {
addr, e := btcaddr.Decode(addrStr, chainParams)
if e != nil {
return nil, fmt.Errorf("cannot decode address: %s", e)
}
pkScript, e := txscript.PayToAddrScript(addr)
if e != nil {
return nil, fmt.Errorf("cannot create txout script: %s", e)
}
outputs = append(outputs, wire.NewTxOut(int64(amt), pkScript))
}
return outputs, nil
}
// SendPairs creates and sends payment transactions. It returns the transaction hash in string format upon success All
// errors are returned in json.RPCError format
func SendPairs(
w *Wallet, amounts map[string]amt.Amount,
account uint32, minconf int32, feeSatPerKb amt.Amount,
) (string, error) {
outputs, e := MakeOutputs(amounts, w.ChainParams())
if e != nil {
return "", e
}
var txHash *chainhash.Hash
txHash, e = w.SendOutputs(outputs, account, minconf, feeSatPerKb)
if e != nil {
if e == txrules.ErrAmountNegative {
return "", ErrNeedPositiveAmount
}
if waddrmgr.IsError(e, waddrmgr.ErrLocked) {
return "", &ErrWalletUnlockNeeded
}
switch e.(type) {
case btcjson.RPCError:
return "", e
}
return "", &btcjson.RPCError{
Code: btcjson.ErrRPCInternal.Code,
Message: e.Error(),
}
}
txHashStr := txHash.String()
I.Ln("successfully sent transaction", txHashStr)
return txHashStr, nil
}
func IsNilOrEmpty(s *string) bool {
return s == nil || *s == ""
}
// SendFrom handles a sendfrom RPC request by creating a new transaction spending unspent transaction outputs for a
// wallet to another 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 SendFrom(icmd interface{}, w *Wallet, chainClient *chainclient.RPCClient) (interface{}, error) {
cmd, ok := icmd.(*btcjson.SendFromCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["sendfrom"],
// "invalid subcommand for addnode",
}
}
// Transaction comments are not yet supported. ScriptError instead of pretending to save them.
if !IsNilOrEmpty(cmd.Comment) || !IsNilOrEmpty(cmd.CommentTo) {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCUnimplemented,
Message: "Transaction comments are not yet supported",
}
}
account, e := w.AccountNumber(
waddrmgr.KeyScopeBIP0044, cmd.FromAccount,
)
if e != nil {
return nil, e
}
// Chk that signed integer parameters are positive.
if cmd.Amount < 0 {
return nil, ErrNeedPositiveAmount
}
minConf := int32(*cmd.MinConf)
if minConf < 0 {
return nil, ErrNeedPositiveMinconf
}
// Create map of address and amount pairs.
amount, e := amt.NewAmount(cmd.Amount)
if e != nil {
return nil, e
}
pairs := map[string]amt.Amount{
cmd.ToAddress: amount,
}
return SendPairs(
w, pairs, account, minConf,
txrules.DefaultRelayFeePerKb,
)
}
// SendMany handles a sendmany RPC request by creating a new transaction spending unspent transaction outputs for a
// wallet to any number of 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(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.SendManyCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["sendmany"],
// "invalid subcommand for addnode",
}
}
// Transaction comments are not yet supported. ScriptError instead of pretending to save them.
if !IsNilOrEmpty(cmd.Comment) {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCUnimplemented,
Message: "Transaction comments are not yet supported",
}
}
account, e := w.AccountNumber(waddrmgr.KeyScopeBIP0044, cmd.FromAccount)
if e != nil {
return nil, e
}
// Chk that minconf is positive.
minConf := int32(*cmd.MinConf)
if minConf < 0 {
return nil, ErrNeedPositiveMinconf
}
// Recreate address/amount pairs, using dcrutil.Amount.
pairs := make(map[string]amt.Amount, len(cmd.Amounts))
for k, v := range cmd.Amounts {
amt, e := amt.NewAmount(v)
if e != nil {
return nil, e
}
pairs[k] = amt
}
return SendPairs(w, pairs, account, minConf, txrules.DefaultRelayFeePerKb)
}
// SendToAddress handles a sendtoaddress RPC request by creating a new transaction spending unspent transaction outputs
// for a wallet to another 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(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.SendToAddressCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["sendtoaddress"],
// "invalid subcommand for addnode",
}
}
// Transaction comments are not yet supported. ScriptError instead of
// pretending to save them.
if !IsNilOrEmpty(cmd.Comment) || !IsNilOrEmpty(cmd.CommentTo) {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCUnimplemented,
Message: "Transaction comments are not yet supported",
}
}
amount, e := amt.NewAmount(cmd.Amount)
if e != nil {
D.Ln(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", e)
return nil, e
}
// Chk that signed integer parameters are positive.
if amount < 0 {
D.Ln(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> need positive amount")
return nil, ErrNeedPositiveAmount
}
// Mock up map of address and amount pairs.
pairs := map[string]amt.Amount{
cmd.Address: amount,
}
// sendtoaddress always spends from the default account, this matches bitcoind
return SendPairs(
w, pairs, waddrmgr.DefaultAccountNum, 1,
txrules.DefaultRelayFeePerKb,
)
}
// SetTxFee sets the transaction fee per kilobyte added to transactions.
func SetTxFee(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.SetTxFeeCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["settxfee"],
// "invalid subcommand for addnode",
}
}
// Chk that amount is not negative.
if cmd.Amount < 0 {
return nil, ErrNeedPositiveAmount
}
// A boolean true result is returned upon success.
return true, nil
}
// SignMessage signs the given message with the private key for the given address
func SignMessage(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.SignMessageCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["signmessage"],
// "invalid subcommand for addnode",
}
}
addr, e := DecodeAddress(cmd.Address, w.ChainParams())
if e != nil {
return nil, e
}
privKey, e := w.PrivKeyForAddress(addr)
if e != nil {
return nil, e
}
var buf bytes.Buffer
e = wire.WriteVarString(&buf, 0, "Bitcoin Signed Message:\n")
if e != nil {
D.Ln(e)
}
e = wire.WriteVarString(&buf, 0, cmd.Message)
if e != nil {
D.Ln(e)
}
messageHash := chainhash.DoubleHashB(buf.Bytes())
sigbytes, e := ecc.SignCompact(
ecc.S256(), privKey,
messageHash, true,
)
if e != nil {
return nil, e
}
return base64.StdEncoding.EncodeToString(sigbytes), nil
}
// SignRawTransaction handles the signrawtransaction command.
func SignRawTransaction(
icmd interface{}, w *Wallet,
cc ...*chainclient.RPCClient,
) (interface{}, error) {
if len(cc) < 1 || cc[0] == nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCNoChain,
Message: "there is currently no chain client to get this response",
}
}
chainClient := cc[0]
cmd, ok := icmd.(*btcjson.SignRawTransactionCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["signrawtransaction"],
// "invalid subcommand for addnode",
}
}
serializedTx, e := DecodeHexStr(cmd.RawTx)
if e != nil {
return nil, e
}
var tx wire.MsgTx
e = tx.Deserialize(bytes.NewBuffer(serializedTx))
if e != nil {
e = errors.New("TX decode failed")
return nil, DeserializationError{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}
}
// TODO: really we probably should look these up with pod anyway to
// make sure that they match the blockchain if present.
inputs := make(map[wire.OutPoint][]byte)
scripts := make(map[string][]byte)
var cmdInputs []btcjson.RawTxInput
if cmd.Inputs != nil {
cmdInputs = *cmd.Inputs
}
for _, rti := range cmdInputs {
var inputHash *chainhash.Hash
inputHash, e = chainhash.NewHashFromStr(rti.Txid)
if e != nil {
return nil, DeserializationError{e}
}
var script []byte
script, e = DecodeHexStr(rti.ScriptPubKey)
if e != nil {
return nil, e
}
// redeemScript is only actually used iff the user provided private keys. In which case, it is used to get the
// scripts for signing. If the user did not provide keys then we always get scripts from the wallet.
//
// Empty strings are ok for this one and hex.DecodeString will DTRT.
if cmd.PrivKeys != nil && len(*cmd.PrivKeys) != 0 {
var redeemScript []byte
redeemScript, e = DecodeHexStr(rti.RedeemScript)
if e != nil {
return nil, e
}
var addr *btcaddr.ScriptHash
addr, e = btcaddr.NewScriptHash(
redeemScript,
w.ChainParams(),
)
if e != nil {
return nil, DeserializationError{e}
}
scripts[addr.String()] = redeemScript
}
inputs[wire.OutPoint{
Hash: *inputHash,
Index: rti.Vout,
}] = script
}
// Now we go and look for any inputs that we were not provided by querying pod with getrawtransaction. We queue up a
// bunch of async requests and will wait for replies after we have checked the rest of the arguments.
requested := make(map[wire.OutPoint]rpcclient.FutureGetTxOutResult)
for _, txIn := range tx.TxIn {
// Did we get this outpoint from the arguments?
if _, ok := inputs[txIn.PreviousOutPoint]; ok {
continue
}
// Asynchronously request the output script.
requested[txIn.PreviousOutPoint] = chainClient.GetTxOutAsync(
&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index,
true,
)
}
// Parse list of private keys, if present. If there are any keys here 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]*util.WIF
if cmd.PrivKeys != nil {
keys = make(map[string]*util.WIF)
for _, key := range *cmd.PrivKeys {
var wif *util.WIF
wif, e = util.DecodeWIF(key)
if e != nil {
return nil, DeserializationError{e}
}
if !wif.IsForNet(w.ChainParams()) {
s := "key network doesn't match wallet's"
return nil, DeserializationError{errors.New(s)}
}
var addr *btcaddr.PubKey
addr, e = btcaddr.NewPubKey(
wif.SerializePubKey(),
w.ChainParams(),
)
if e != nil {
return nil, DeserializationError{e}
}
keys[addr.EncodeAddress()] = wif
}
}
// We have checked the rest of the args. now we can collect the async txs.
//
// TODO: If we don't mind the possibility of wasting work we could move waiting to the following loop and be
// slightly more asynchronous.
for outPoint, resp := range requested {
var result *btcjson.GetTxOutResult
result, e = resp.Receive()
if e != nil {
return nil, e
}
var script []byte
if script, e = hex.DecodeString(result.ScriptPubKey.Hex); E.Chk(e) {
return nil, e
}
inputs[outPoint] = script
}
// All args collected. Now we can sign all the inputs that we can. `complete' denotes that we successfully signed
// all outputs and that all scripts will run to completion. This is returned as part of the reply.
var signErrs []SignatureError
signErrs, e = w.SignTransaction(&tx, hashType, inputs, keys, scripts)
if e != nil {
return nil, e
}
var buf bytes.Buffer
buf.Grow(tx.SerializeSize())
// All returned errors (not OOM, which panics) encountered during bytes.Buffer writes are unexpected.
if e = tx.Serialize(&buf); E.Chk(e) {
panic(e)
}
signErrors := make([]btcjson.SignRawTransactionError, 0, len(signErrs))
for _, ee := range signErrs {
input := tx.TxIn[ee.InputIndex]
signErrors = append(
signErrors, btcjson.SignRawTransactionError{
TxID: input.PreviousOutPoint.Hash.String(),
Vout: input.PreviousOutPoint.Index,
ScriptSig: hex.EncodeToString(input.SignatureScript),
Sequence: input.Sequence,
Error: e.Error(),
},
)
}
return btcjson.SignRawTransactionResult{
Hex: hex.EncodeToString(buf.Bytes()),
Complete: len(signErrors) == 0,
Errors: signErrors,
}, nil
}
// ValidateAddress handles the validateaddress command.
func ValidateAddress(icmd interface{}, w *Wallet, chainClient ...*chainclient.RPCClient) (interface{}, error) {
cmd, ok := icmd.(*btcjson.ValidateAddressCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["validateaddress"],
// "invalid subcommand for addnode",
}
}
result := btcjson.ValidateAddressWalletResult{}
addr, e := DecodeAddress(cmd.Address, w.ChainParams())
if e != nil {
// Use result zero value (IsValid=false).
return result, nil
}
// We could put whether or not the address is a script here, by checking the type of "addr", however, the reference
// implementation only puts that information if the script is "ismine", and we follow that behaviour.
result.Address = addr.EncodeAddress()
result.IsValid = true
ainfo, e := w.AddressInfo(addr)
if e != nil {
if waddrmgr.IsError(e, waddrmgr.ErrAddressNotFound) {
// No additional information available about the address.
return result, nil
}
return nil, e
}
// The address lookup was successful which means there is further information about it available and it is "mine".
result.IsMine = true
acctName, e := w.AccountName(waddrmgr.KeyScopeBIP0044, ainfo.Account())
if e != nil {
return nil, &ErrAccountNameNotFound
}
result.Account = acctName
switch ma := ainfo.(type) {
case waddrmgr.ManagedPubKeyAddress:
result.IsCompressed = ma.Compressed()
result.PubKey = ma.ExportPubKey()
case waddrmgr.ManagedScriptAddress:
result.IsScript = true
// The script is only available if the manager is unlocked, so just break out now if there is an error.
script, e := ma.Script()
if e != nil {
break
}
result.Hex = hex.EncodeToString(script)
// This typically shouldn't fail unless an invalid script was imported.
//
// However, if it fails for any reason, there is no further information available, so just set the script type a
// non-standard and break out now.
class, addrs, reqSigs, e := txscript.ExtractPkScriptAddrs(
script, w.ChainParams(),
)
if e != nil {
result.Script = txscript.NonStandardTy.String()
break
}
addrStrings := make([]string, len(addrs))
for i, a := range addrs {
addrStrings[i] = a.EncodeAddress()
}
result.Addresses = addrStrings
// Multi-signature scripts also provide the number of required
// signatures.
result.Script = class.String()
if class == txscript.MultiSigTy {
result.SigsRequired = int32(reqSigs)
}
}
return result, nil
}
// VerifyMessage handles the verifymessage command by verifying the provided compact signature for the given address and
// message.
func VerifyMessage(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.VerifyMessageCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["verifymessage"],
// "invalid subcommand for addnode",
}
}
addr, e := DecodeAddress(cmd.Address, w.ChainParams())
if e != nil {
return nil, e
}
// decode base64 signature
sig, e := base64.StdEncoding.DecodeString(cmd.Signature)
if e != nil {
return nil, e
}
// Validate the signature - this just shows that it was valid at all. we will compare it with the key next.
var buf bytes.Buffer
e = wire.WriteVarString(&buf, 0, "Parallelcoin Signed Message:\n")
if e != nil {
D.Ln(e)
}
e = wire.WriteVarString(&buf, 0, cmd.Message)
if e != nil {
D.Ln(e)
}
expectedMessageHash := chainhash.DoubleHashB(buf.Bytes())
pk, wasCompressed, e := ecc.RecoverCompact(
ecc.S256(), sig,
expectedMessageHash,
)
if e != nil {
return nil, e
}
var serializedPubKey []byte
if wasCompressed {
serializedPubKey = pk.SerializeCompressed()
} else {
serializedPubKey = pk.SerializeUncompressed()
}
// Verify that the signed-by address matches the given address
switch checkAddr := addr.(type) {
case *btcaddr.PubKeyHash: // ok
return bytes.Equal(btcaddr.Hash160(serializedPubKey), checkAddr.Hash160()[:]), nil
case *btcaddr.PubKey: // ok
return string(serializedPubKey) == checkAddr.String(), nil
default:
return nil, errors.New("address type not supported")
}
}
// WalletIsLocked handles the walletislocked extension request by returning the current lock state (false for unlocked,
// true for locked) of an account.
func WalletIsLocked(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (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(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
w.Lock()
return nil, nil
}
// 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(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.WalletPassphraseCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["walletpassphrase"],
// "invalid subcommand for addnode",
}
}
timeout := time.Second * time.Duration(cmd.Timeout)
var unlockAfter <-chan time.Time
if timeout != 0 {
unlockAfter = time.After(timeout)
}
e := w.Unlock([]byte(cmd.Passphrase), unlockAfter)
return nil, e
}
// WalletPassphraseChange responds to the walletpassphrasechange request by unlocking all accounts with the provided old
// passphrase, and re-encrypting each private key with an AES key derived from the new passphrase.
//
// If the old passphrase is correct and the passphrase is changed, all wallets will be immediately locked.
func WalletPassphraseChange(
icmd interface{}, w *Wallet,
chainClient ...*chainclient.RPCClient,
) (interface{}, error) {
cmd, ok := icmd.(*btcjson.WalletPassphraseChangeCmd)
if !ok {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: HelpDescsEnUS()["walletpassphrasechange"],
// "invalid subcommand for addnode",
}
}
e := w.ChangePrivatePassphrase(
[]byte(cmd.OldPassphrase),
[]byte(cmd.NewPassphrase),
)
if waddrmgr.IsError(e, waddrmgr.ErrWrongPassphrase) {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCWalletPassphraseIncorrect,
Message: "Incorrect passphrase",
}
}
return nil, e
}
// DecodeHexStr decodes the hex encoding of a string, possibly prepending a leading '0' character if there is an odd
// number of bytes in the hex string. This is to prevent an error for an invalid hex string when using an odd number of
// bytes when calling hex.Decode.
func DecodeHexStr(hexStr string) ([]byte, error) {
if len(hexStr)%2 != 0 {
hexStr = "0" + hexStr
}
decoded, e := hex.DecodeString(hexStr)
if e != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCDecodeHexString,
Message: "Hex string decode failed: " + e.Error(),
}
}
return decoded, nil
}