Skip to content

Commit

Permalink
Add consolidate command handling to the wallet JSON RPC
Browse files Browse the repository at this point in the history
This adds consolidate command handling to the JSON RPC. The command takes
an integer representing the number of inputs you would like to compress
and then produces a transaction compressing them. The transaction hash
is returned in response.
  • Loading branch information
cjepson committed Mar 4, 2016
1 parent 9f1b598 commit 833ae9b
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 15 deletions.
6 changes: 6 additions & 0 deletions internal/rpchelp/helpdescs_en_US.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ var helpDescsEnUS = map[string]string{
"addmultisigaddress-nrequired": "The number of signatures required to redeem outputs paid to this address",
"addmultisigaddress--result0": "The imported pay-to-script-hash address",

// ConsolidateCmd help.
"consolidate--synopsis": "Consolidate n many UTXOs into a single output in the wallet.",
"consolidate-inputs": "Number of UTXOs to consolidate as inputs",
"consolidate-account": "Optional: Account to use",
"consolidate--result0": "Transaction hash for the consolidation transaction",

// CreateMultisigCmd help.
"createmultisig--synopsis": "Generate a multisig address and redeem script.",
"createmultisig-keys": "Pubkeys and/or pay-to-pubkey-hash addresses to partially control the multisig address",
Expand Down
1 change: 1 addition & 0 deletions internal/rpchelp/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var Methods = []struct {
ResultTypes []interface{}
}{
{"addmultisigaddress", returnsString},
{"consolidate", returnsString},
{"createmultisig", []interface{}{(*dcrjson.CreateMultiSigResult)(nil)}},
{"dumpprivkey", returnsString},
{"getaccount", returnsString},
Expand Down
16 changes: 16 additions & 0 deletions rpc/legacyrpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ var rpcHandlers = map[string]struct {
}{
// Reference implementation wallet methods (implemented)
"addmultisigaddress": {handlerWithChain: AddMultiSigAddress},
"consolidate": {handler: Consolidate},
"createmultisig": {handler: CreateMultiSig},
"dumpprivkey": {handler: DumpPrivKey, requireUnsafeOnMainNet: true},
"getaccount": {handler: GetAccount},
Expand Down Expand Up @@ -413,6 +414,21 @@ func AddMultiSigAddress(icmd interface{}, w *wallet.Wallet, chainClient *chain.R
return addr.Address().EncodeAddress(), nil
}

// Consolidate handles a consolidate request by returning attempting to compress
// as many inputs as given and then returning the txHash and error.
func Consolidate(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*dcrjson.ConsolidateCmd)

// TODO In the future this should take the optional account and
// only consolidate UTXOs found within that account.
txHash, err := w.Consolidate(cmd.Inputs)
if err != nil {
return nil, err
}

return txHash.String(), nil
}

// CreateMultiSig handles an createmultisig request by returning a
// multisig address for the given inputs.
func CreateMultiSig(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
Expand Down
3 changes: 2 additions & 1 deletion rpc/legacyrpc/rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package legacyrpc
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) DEPRECATED -- Unused (all imported addresses belong to the imported account)\n\nResult:\n\"value\" (string) The imported pay-to-script-hash address\n",
"consolidate": "consolidate inputs (\"account\")\n\nConsolidate n many UTXOs into a single output in the wallet.\n\nArguments:\n1. inputs (numeric, required) Number of UTXOs to consolidate as inputs\n2. account (string, optional) Optional: Account to use\n\nResult:\n\"value\" (string) Transaction hash for the consolidation transaction\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",
Expand Down Expand Up @@ -79,4 +80,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 \"balancetype\")\ngetbestblockhash\ngetblockcount\ngetinfo\ngetgenerate\ngetmasterpubkey\ngetmultisigoutinfo \"hash\" index\ngetseed\ngetnewaddress (\"account\" verbose=false)\ngetrawchangeaddress (\"account\" verbose=false)\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettickets includeimmature\ngetticketmaxprice\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nimportscript \"hex\"\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,\"tree\":n},...]\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsetgenerate generate (genproclimit=-1)\nsetticketmaxprice max\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\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\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" \"comment\")\nsendtossrtx \"fromaccount\" \"tickethash\" (\"comment\")\nsendtosstx \"fromaccount\" amounts [{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"amt\":n},...] [{\"addr\":\"value\",\"commitamt\":n,\"changeaddr\":\"value\",\"changeamt\":n},...] (minconf=1 \"comment\")\nsendtossgen \"fromaccount\" \"tickethash\" \"blockhash\" height votebits (\"comment\")\ngetticketvotebits \"txhash\"\ngetticketsvotebits [\"txhash\",...]\nsetticketvotebits \"txhash\" votebits (\"votebitsext\")\ngetstakeinfo\ngetticketfee\nsetticketfee fee"
var requestUsages = "addmultisigaddress nrequired [\"key\",...] (\"account\")\nconsolidate inputs (\"account\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1 \"balancetype\")\ngetbestblockhash\ngetblockcount\ngetinfo\ngetgenerate\ngetmasterpubkey\ngetmultisigoutinfo \"hash\" index\ngetseed\ngetnewaddress (\"account\" verbose=false)\ngetrawchangeaddress (\"account\" verbose=false)\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettickets includeimmature\ngetticketmaxprice\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nimportscript \"hex\"\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,\"tree\":n},...]\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsetgenerate generate (genproclimit=-1)\nsetticketmaxprice max\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\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\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" \"comment\")\nsendtossrtx \"fromaccount\" \"tickethash\" (\"comment\")\nsendtosstx \"fromaccount\" amounts [{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"amt\":n},...] [{\"addr\":\"value\",\"commitamt\":n,\"changeaddr\":\"value\",\"changeamt\":n},...] (minconf=1 \"comment\")\nsendtossgen \"fromaccount\" \"tickethash\" \"blockhash\" height votebits (\"comment\")\ngetticketvotebits \"txhash\"\ngetticketsvotebits [\"txhash\",...]\nsetticketvotebits \"txhash\" votebits (\"votebitsext\")\ngetstakeinfo\ngetticketfee\nsetticketfee fee"
28 changes: 14 additions & 14 deletions wallet/createtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -818,21 +818,21 @@ func (w *Wallet) txToMultisig(account uint32, amount dcrutil.Amount,

// compressWallet compresses all the utxos in a wallet into a single change
// address. For use when it becomes dusty.
func (w *Wallet) compressWallet(maxNumIns int) error {
func (w *Wallet) compressWallet(maxNumIns int) (*chainhash.Hash, error) {
chainClient, err := w.requireChainClient()
if err != nil {
return err
return nil, err
}

isReorganizing, _ := chainClient.GetReorganizing()
if isReorganizing {
return ErrBlockchainReorganizing
return nil, ErrBlockchainReorganizing
}

// Get current block's height and hash.
bs, err := chainClient.BlockStamp()
if err != nil {
return err
return nil, err
}

// Initialize the address pool for use.
Expand All @@ -853,11 +853,11 @@ func (w *Wallet) compressWallet(maxNumIns int) error {
minconf := int32(1)
eligible, err := w.findEligibleOutputs(account, minconf, bs)
if err != nil {
return err
return nil, err
}

if len(eligible) == 0 {
return ErrNoOutsToConsolidate
return nil, ErrNoOutsToConsolidate
}

txInCount := len(eligible)
Expand Down Expand Up @@ -894,42 +894,42 @@ func (w *Wallet) compressWallet(maxNumIns int) error {

changeAddr, err := addrFunc()
if err != nil {
return err
return nil, err
}

pkScript, err := txscript.PayToAddrScript(changeAddr)
if err != nil {
return fmt.Errorf("cannot create txout script: %s", err)
return nil, fmt.Errorf("cannot create txout script: %s", err)
}
msgtx.AddTxOut(wire.NewTxOut(int64(outputAmt), pkScript))

if err = signMsgTx(msgtx, forSigning, w.Manager,
w.chainParams); err != nil {
return err
return nil, err
}
if err := validateMsgTx(msgtx, forSigning); err != nil {
return err
return nil, err
}

txSha, err := chainClient.SendRawTransaction(msgtx, false)
if err != nil {
return err
return nil, err
}
txSucceeded = true

// Insert the transaction and credits into the transaction manager.
rec, err := w.insertIntoTxMgr(msgtx)
if err != nil {
return err
return nil, err
}
err = w.insertCreditsIntoTxMgr(msgtx, rec)
if err != nil {
return err
return nil, err
}

log.Infof("Successfully consolidated funds in transaction %v", txSha)

return nil
return txSha, nil
}

// compressEligible compresses all the utxos passed to it into a single
Expand Down
27 changes: 27 additions & 0 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ type Wallet struct {
rescanFinished chan *RescanFinishedMsg

// Channel for transaction creation requests.
consolidateRequests chan consolidateRequest
createTxRequests chan createTxRequest
createMultisigTxRequests chan createMultisigTxRequest

Expand Down Expand Up @@ -237,6 +238,7 @@ func newWallet(vb uint16, esm bool, btm dcrutil.Amount, addressReuse bool,
rescanNotifications: make(chan interface{}),
rescanProgress: make(chan *RescanProgressMsg),
rescanFinished: make(chan *RescanFinishedMsg),
consolidateRequests: make(chan consolidateRequest),
createTxRequests: make(chan createTxRequest),
createMultisigTxRequests: make(chan createMultisigTxRequest),
createSStxRequests: make(chan createSStxRequest),
Expand Down Expand Up @@ -1322,6 +1324,10 @@ func (w *Wallet) syncWithChain() error {
}

type (
consolidateRequest struct {
inputs int
resp chan consolidateResponse
}
createTxRequest struct {
account uint32
pairs map[string]dcrutil.Amount
Expand Down Expand Up @@ -1363,6 +1369,10 @@ type (
resp chan purchaseTicketResponse
}

consolidateResponse struct {
txHash *chainhash.Hash
err error
}
createTxResponse struct {
tx *CreatedTx
err error
Expand Down Expand Up @@ -1406,6 +1416,10 @@ func (w *Wallet) txCreator() {
out:
for {
select {
case txr := <-w.consolidateRequests:
txh, err := w.compressWallet(txr.inputs)
txr.resp <- consolidateResponse{txh, err}

case txr := <-w.createTxRequests:
// Initialize the address pool for use.
pool := w.internalPool
Expand Down Expand Up @@ -1472,6 +1486,19 @@ out:
w.wg.Done()
}

// Consolidate consolidates as many UTXOs as are passed in the inputs argument.
// If that many UTXOs can not be found, it will use the maximum it finds. This
// will only compress UTXOs in the default account
func (w *Wallet) Consolidate(inputs int) (*chainhash.Hash, error) {
req := consolidateRequest{
inputs: inputs,
resp: make(chan consolidateResponse),
}
w.consolidateRequests <- req
resp := <-req.resp
return resp.txHash, resp.err
}

// CreateSimpleTx creates a new signed transaction spending unspent P2PKH
// outputs with at laest minconf confirmations spending to any number of
// address/amount pairs. Change and an appropiate transaction fee are
Expand Down

0 comments on commit 833ae9b

Please sign in to comment.