Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SelectUnspent JSON-RPC method #2079

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 61 additions & 0 deletions internal/rpc/jsonrpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ var handlers = map[string]handler{
"listsinceblock": {fn: (*Server).listSinceBlock},
"listtransactions": {fn: (*Server).listTransactions},
"listunspent": {fn: (*Server).listUnspent},
"selectunspent": {fn: (*Server).selectUnspent},
"lockaccount": {fn: (*Server).lockAccount},
"lockunspent": {fn: (*Server).lockUnspent},
"mixaccount": {fn: (*Server).mixAccount},
Expand Down Expand Up @@ -3081,6 +3082,66 @@ func (s *Server) listUnspent(ctx context.Context, icmd interface{}) (interface{}
return result, nil
}

// selectUnspent handles the selectunspent command.
func (s *Server) selectUnspent(ctx context.Context, icmd interface{}) (interface{}, error) {
cmd := icmd.(*types.SelectUnspentCmd)
w, ok := s.walletLoader.LoadedWallet()
if !ok {
return nil, errUnloadedWallet
}

targetAmount, err := dcrutil.NewAmount(cmd.TargetAmount)
if err != nil {
return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err)
}
if targetAmount < 0 {
return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative target amount")
}

var minAmount dcrutil.Amount
if cmd.MinAmount != nil {
minAmount, err = dcrutil.NewAmount(*cmd.MinAmount)
if err != nil {
return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err)
}
}

if minAmount < 0 {
return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative min amount")
}

if minAmount > targetAmount {
return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "target amount is less than min amount")
}

var account string
if cmd.Account != nil {
account = *cmd.Account
}

var spendAll = false
if cmd.SpendAll != nil {
spendAll = *cmd.SpendAll
}

var inputMethod = types.RandomInputSelection
if cmd.InputSelectionMethod != nil {
inputMethod = types.InputSelectionMethod(*cmd.InputSelectionMethod)
}

seenTxAddress := make(map[string]struct{})
if cmd.SeenTxAddress != nil {
seenTxAddress = *cmd.SeenTxAddress
}

result, err := w.SelectUnspent(ctx, targetAmount, minAmount, int32(*cmd.MinConf), account,
spendAll, seenTxAddress, inputMethod)
if err != nil {
return nil, err
}
return result, nil
}

// lockUnspent handles the lockunspent command.
func (s *Server) lockUnspent(ctx context.Context, icmd interface{}) (interface{}, error) {
cmd := icmd.(*types.LockUnspentCmd)
Expand Down
3 changes: 2 additions & 1 deletion internal/rpc/jsonrpc/rpcserverhelp.go

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions internal/rpchelp/helpdescs_en_US.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,19 @@ var helpDescsEnUS = map[string]string{
"listunspentresult-txtype": "The type of the transaction",
"listunspentresult-tree": "The tree the transaction comes from",

// SelectUnspentCmd help.
"selectunspent--synopsis": "Returns a JSON array of objects representing unlocked unspent outputs controlled by wallet keys that are enough to pay target amount.",
"selectunspent-targetamount": "The minimum total output value of all returned inputs",
"selectunspent-minamount": "The minimum amount output value of transaction output should have before it is considered",
"selectunspent-minconf": "Minimum block confirmations required for a utxo to be considered",
"selectunspent-account": "If set, only return unspent outputs from this account",
"selectunspent-spendall": "If set, all eligible inputs will be returned. (target amount will be ignored)",
"selectunspent-inputselectionmethod": "The method for how transaction inputs should be selected.",
beansgum marked this conversation as resolved.
Show resolved Hide resolved
"selectunspent-seentxaddress": "Addresses or transaction hashes to be skipped when using UniqueTxInputSelection",
beansgum marked this conversation as resolved.
Show resolved Hide resolved
"selectunspent-seentxaddress--desc": "JSON object using addresses or transaction hashes as keys and empty structs as values to specify seen utxos",
"selectunspent-seentxaddress--key": "Address or transaction hash",
"selectunspent-seentxaddress--value": "Empty struct",

// LockAccountCmd help.
"lockaccount--synopsis": "Lock an individually-encrypted account",
"lockaccount-account": "Account to lock",
Expand Down
1 change: 1 addition & 0 deletions internal/rpchelp/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ var Methods = []struct {
{"listsinceblock", []interface{}{(*types.ListSinceBlockResult)(nil)}},
{"listtransactions", returnsLTRArray},
{"listunspent", []interface{}{(*types.ListUnspentResult)(nil)}},
{"selectunspent", []interface{}{(*types.ListUnspentResult)(nil)}},
{"lockaccount", nil},
{"lockunspent", returnsBool},
{"mixaccount", nil},
Expand Down
7 changes: 7 additions & 0 deletions rpc/client/dcrwallet/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ func (c *Client) ListUnspentMinMaxAddresses(ctx context.Context, minConf, maxCon
return res, err
}

func (c *Client) SelectUnspent(ctx context.Context, targetAmount, minAmount dcrutil.Amount, minConf int,
account string, spendAll bool, inputSelectionMethod string, seenTxAddress map[string]struct{}) ([]types.ListUnspentResult, error) {
var res []types.ListUnspentResult
err := c.Call(ctx, "selectunspent", &res, targetAmount.ToCoin(), minAmount.ToCoin(), minConf, account, spendAll, inputSelectionMethod, seenTxAddress)
return res, err
}

// ListSinceBlock returns all transactions added in blocks since the specified
// block hash, or all transactions if it is nil, using the default number of
// minimum confirmations as a filter.
Expand Down
24 changes: 24 additions & 0 deletions rpc/jsonrpc/types/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,29 @@ func NewListUnspentCmd(minConf, maxConf *int, addresses *[]string) *ListUnspentC
}
}

type SelectUnspentCmd struct {
TargetAmount float64
MinAmount *float64 `jsonrpcdefault:"0"`
MinConf *int `jsonrpcdefault:"1"`
Account *string `jsonrpcdefault:"\"\""`
SpendAll *bool `jsonrpcdefault:"false"`
InputSelectionMethod *string `jsonrpcdefault:"\"random\""`
SeenTxAddress *map[string]struct{} `jsonrpcusage:"{\"address\":{},\"txhash\":{},...}"`
}

func NewSelectUnspentCmd(targetAmount float64, minAmount *float64, minConf *int, account *string,
spendAll *bool, inputSelectionMethod *string, seenTxAddress *map[string]struct{}) *SelectUnspentCmd {
return &SelectUnspentCmd{
TargetAmount: targetAmount,
MinConf: minConf,
MinAmount: minAmount,
Account: account,
SpendAll: spendAll,
InputSelectionMethod: inputSelectionMethod,
SeenTxAddress: seenTxAddress,
}
}

// LockUnspentCmd defines the lockunspent JSON-RPC command.
type LockUnspentCmd struct {
Unlock bool
Expand Down Expand Up @@ -1234,6 +1257,7 @@ func init() {
{"listsinceblock", (*ListSinceBlockCmd)(nil)},
{"listtransactions", (*ListTransactionsCmd)(nil)},
{"listunspent", (*ListUnspentCmd)(nil)},
{"selectunspent", (*SelectUnspentCmd)(nil)},
{"lockaccount", (*LockAccountCmd)(nil)},
{"lockunspent", (*LockUnspentCmd)(nil)},
{"mixaccount", (*MixAccountCmd)(nil)},
Expand Down
17 changes: 17 additions & 0 deletions rpc/jsonrpc/types/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,23 @@ type ListSinceBlockResult struct {
LastBlock string `json:"lastblock"`
}

// InputSelectionMethod defines the type used in the selectunspent JSON-RPC
// result for the InputSelectionMethod command field.
type InputSelectionMethod string

const (
// RandomInputSelection indicates any random utxo can be selected.
RandomInputSelection InputSelectionMethod = "random"
// RandomAddressInputSelection indicates that only utxos matching a randomly selected
// address should be selected.
RandomAddressInputSelection InputSelectionMethod = "randomaddress"
// OneUTXOInputSelection indicates that only one utxo should be selected.
OneUTXOInputSelection InputSelectionMethod = "oneutxo"
// UniqueTxInputSelection indicates that only utxos with unique address
// and hash should be selected.
UniqueTxInputSelection InputSelectionMethod = "uniquetx"
)

// ListUnspentResult models a successful response from the listunspent request.
// Contains Decred additions.
type ListUnspentResult struct {
Expand Down