diff --git a/internal/rpc/jsonrpc/methods.go b/internal/rpc/jsonrpc/methods.go index 37afae274..a16b481af 100644 --- a/internal/rpc/jsonrpc/methods.go +++ b/internal/rpc/jsonrpc/methods.go @@ -26,6 +26,7 @@ import ( "decred.org/dcrwallet/v5/rpc/client/dcrd" "decred.org/dcrwallet/v5/rpc/jsonrpc/types" "decred.org/dcrwallet/v5/spv" + "decred.org/dcrwallet/v5/ticketbuyer" "decred.org/dcrwallet/v5/version" "decred.org/dcrwallet/v5/wallet" "decred.org/dcrwallet/v5/wallet/txauthor" @@ -152,6 +153,7 @@ var handlers = map[string]handler{ "redeemmultisigouts": {fn: (*Server).redeemMultiSigOuts}, "renameaccount": {fn: (*Server).renameAccount}, "rescanwallet": {fn: (*Server).rescanWallet}, + "runaccountmixer": {fn: (*Server).runAccountMixer}, "sendfrom": {fn: (*Server).sendFrom}, "sendfromtreasury": {fn: (*Server).sendFromTreasury}, "sendmany": {fn: (*Server).sendMany}, @@ -5519,6 +5521,37 @@ func (s *Server) mixOutput(ctx context.Context, icmd any) (any, error) { return nil, err } +// runAccountMixer starts a new account mixer for the specified account (and +// branch). +func (s *Server) runAccountMixer(ctx context.Context, icmd any) (any, error) { + cmd := icmd.(*types.RunAccountMixerCmd) + w, ok := s.walletLoader.LoadedWallet() + if !ok { + return nil, errUnloadedWallet + } + + tb := ticketbuyer.New(w, ticketbuyer.Config{ + Mixing: true, + MixedAccountBranch: cmd.MixedAccountBranch, + MixedAccount: cmd.MixedAccount, + ChangeAccount: cmd.ChangeAccount, + BuyTickets: false, + MixChange: true, + }) + + // Start ticketbuyer in the background rather than blocking the rpc request. + // Use the server waitgroup to ensure Run can return cleanly rather than + // being killed mid database transaction. + s.wg.Add(1) + go func() { + defer s.wg.Done() + serverCtx := s.httpServer.BaseContext(nil) + _ = tb.Run(serverCtx, []byte(cmd.Passphrase)) + }() + + return nil, nil +} + func (s *Server) mixAccount(ctx context.Context, icmd any) (any, error) { if !s.cfg.Mixing { return nil, errors.E("Mixing is not configured") diff --git a/internal/rpc/jsonrpc/rpcserverhelp.go b/internal/rpc/jsonrpc/rpcserverhelp.go index 5a6e1d079..e046cbb06 100644 --- a/internal/rpc/jsonrpc/rpcserverhelp.go +++ b/internal/rpc/jsonrpc/rpcserverhelp.go @@ -73,6 +73,7 @@ func helpDescsEnUS() map[string]string { "redeemmultisigouts": "redeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\n\nTakes a hash, looks up all unspent outpoints and generates list artially signed transactions spending to either an address specified or internal addresses\n\nArguments:\n1. fromscraddress (string, required) Input script hash address.\n2. toaddress (string, optional) Address to look for (if not internal addresses).\n3. number (numeric, optional) Number of outpoints found.\n\nResult:\n{\n \"hex\": \"value\", (string) Resulting hash.\n \"complete\": true|false, (boolean) Shows if opperation was completed.\n \"errors\": [{ (array of object) Any errors generated.\n \"txid\": \"value\", (string) The transaction hash of the referenced previous output\n \"vout\": n, (numeric) The output index of the referenced previous output\n \"scriptSig\": \"value\", (string) The hex-encoded signature script\n \"sequence\": n, (numeric) Script sequence number\n \"error\": \"value\", (string) Verification or signing error related to the input\n },...], \n} \n", "renameaccount": "renameaccount \"oldaccount\" \"newaccount\"\n\nRenames an account.\n\nArguments:\n1. oldaccount (string, required) The old account name to rename\n2. newaccount (string, required) The new name for the account\n\nResult:\nNothing\n", "rescanwallet": "rescanwallet (beginheight=0)\n\nRescan the block chain for wallet data, blocking until the rescan completes or exits with an error\n\nArguments:\n1. beginheight (numeric, optional, default=0) The height of the first block to begin the rescan from\n\nResult:\nNothing\n", + "runaccountmixer": "runaccountmixer \"passphrase\" mixedaccount mixedaccountbranch changeaccount\n\nStarts a new account mixer for the specified account (and branch)\n\nArguments:\n1. passphrase (string, required) The private passphrase to unlock the wallet\n2. mixedaccount (numeric, required) The account number to which the mixing funds should end up\n3. mixedaccountbranch (numeric, required) The branch number to which the mixing funds should end up\n4. changeaccount (numeric, required) The account that will be used for any unmixed change that is waiting to be mixed\n\nResult:\nNothing\n", "sendfrom": "sendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\n\nAuthors, signs, and sends a transaction that outputs some amount to a payment address.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. fromaccount (string, required) Account to pick unspent outputs from\n2. toaddress (string, required) Address to pay\n3. amount (numeric, required) Amount to send to the payment address valued in decred\n4. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction output is eligible to be spent\n5. comment (string, optional) Unused\n6. commentto (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", "sendfromtreasury": "sendfromtreasury \"key\" amounts\n\nSend from treasury balance to multiple recipients.\n\nArguments:\n1. key (string, required) Politeia public key\n2. amounts (object, required) Pairs of payment addresses and the output amount to pay each\n{\n \"Address to pay\": Amount to send to the payment address valued in decred, (object) JSON object using payment addresses as keys and output amounts valued in decred to send to each address\n ...\n}\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", "sendmany": "sendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\n\nAuthors, signs, and sends a transaction that outputs to many payment addresses.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. fromaccount (string, required) Account to pick unspent outputs from\n2. amounts (object, required) Pairs of payment addresses and the output amount to pay each\n{\n \"Address to pay\": Amount to send to the payment address valued in decred, (object) JSON object using payment addresses as keys and output amounts valued in decred to send to each address\n ...\n}\n3. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction output is eligible to be spent\n4. comment (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", @@ -113,4 +114,4 @@ var localeHelpDescs = map[string]func() map[string]string{ "en_US": helpDescsEnUS, } -var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naccountunlocked \"account\"\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddtransaction \"blockhash\" \"transaction\"\nauditreuse (since)\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ncreaterawtransaction [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...] {\"address\":amount,...} (locktime expiry)\ncreatesignature \"address\" inputindex hashtype \"previouspkscript\" \"serializedtransaction\"\ndisapprovepercent\ndiscoverusage (\"startblock\" discoveraccounts gaplimit)\ndumpprivkey \"address\"\nfundrawtransaction \"hexstring\" \"fundaccount\" ({\"changeaddress\":changeaddress,\"feerate\":feerate,\"conftarget\":conftarget})\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblock\ngetbestblockhash\ngetblockcount\ngetblockhash index\ngetblockheader \"hash\" (verbose=true)\ngetblock \"hash\" (verbose=true verbosetx=false)\ngetcoinjoinsbyacct\ngetcurrentnet\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetpeerinfo\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngettxout \"txid\" vout tree (includemempool=true)\ngetunconfirmedbalance (\"account\")\ngetvotechoices (\"tickethash\")\ngetwalletfee\ngetcfilterv2 \"blockhash\"\nhelp (\"command\")\nimportcfiltersv2 startheight [\"filter\",...]\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportpubkey \"pubkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nimportxpub \"name\" \"xpub\"\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent (\"account\")\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\",...] \"account\")\nlockaccount \"account\"\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\nmixaccount\nmixoutput \"outpoint\"\nprocessunmanagedticket \"tickethash\"\npurchaseticket \"fromaccount\" spendlimit (minconf=1 numtickets=1 expiry \"comment\" dontsigntx)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanwallet (beginheight=0)\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendfromtreasury \"key\" amounts\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendrawtransaction \"hextx\" (allowhighfees=false)\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsendtotreasury amount\nsetaccountpassphrase \"account\" \"passphrase\"\nsetdisapprovepercent percent\nsettreasurypolicy \"key\" \"policy\" (\"ticket\")\nsettspendpolicy \"hash\" \"policy\" (\"ticket\")\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\" (\"tickethash\")\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nspendoutputs \"account\" [\"previousoutpoint\",...] [{\"address\":\"value\",\"amount\":n.nnn},...]\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nsyncstatus\nticketinfo (startheight=0)\ntreasurypolicy (\"key\" \"ticket\")\ntspendpolicy (\"hash\" \"ticket\")\nunlockaccount \"account\" \"passphrase\"\nvalidateaddress \"address\"\nvalidatepredcp0005cf\nverifymessage \"address\" \"signature\" \"message\"\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpubpassphrasechange \"oldpassphrase\" \"newpassphrase\"" +var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naccountunlocked \"account\"\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddtransaction \"blockhash\" \"transaction\"\nauditreuse (since)\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ncreaterawtransaction [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...] {\"address\":amount,...} (locktime expiry)\ncreatesignature \"address\" inputindex hashtype \"previouspkscript\" \"serializedtransaction\"\ndisapprovepercent\ndiscoverusage (\"startblock\" discoveraccounts gaplimit)\ndumpprivkey \"address\"\nfundrawtransaction \"hexstring\" \"fundaccount\" ({\"changeaddress\":changeaddress,\"feerate\":feerate,\"conftarget\":conftarget})\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblock\ngetbestblockhash\ngetblockcount\ngetblockhash index\ngetblockheader \"hash\" (verbose=true)\ngetblock \"hash\" (verbose=true verbosetx=false)\ngetcoinjoinsbyacct\ngetcurrentnet\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetpeerinfo\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngettxout \"txid\" vout tree (includemempool=true)\ngetunconfirmedbalance (\"account\")\ngetvotechoices (\"tickethash\")\ngetwalletfee\ngetcfilterv2 \"blockhash\"\nhelp (\"command\")\nimportcfiltersv2 startheight [\"filter\",...]\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportpubkey \"pubkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nimportxpub \"name\" \"xpub\"\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent (\"account\")\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\",...] \"account\")\nlockaccount \"account\"\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\nmixaccount\nmixoutput \"outpoint\"\nprocessunmanagedticket \"tickethash\"\npurchaseticket \"fromaccount\" spendlimit (minconf=1 numtickets=1 expiry \"comment\" dontsigntx)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanwallet (beginheight=0)\nrunaccountmixer \"passphrase\" mixedaccount mixedaccountbranch changeaccount\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendfromtreasury \"key\" amounts\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendrawtransaction \"hextx\" (allowhighfees=false)\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsendtotreasury amount\nsetaccountpassphrase \"account\" \"passphrase\"\nsetdisapprovepercent percent\nsettreasurypolicy \"key\" \"policy\" (\"ticket\")\nsettspendpolicy \"hash\" \"policy\" (\"ticket\")\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\" (\"tickethash\")\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nspendoutputs \"account\" [\"previousoutpoint\",...] [{\"address\":\"value\",\"amount\":n.nnn},...]\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nsyncstatus\nticketinfo (startheight=0)\ntreasurypolicy (\"key\" \"ticket\")\ntspendpolicy (\"hash\" \"ticket\")\nunlockaccount \"account\" \"passphrase\"\nvalidateaddress \"address\"\nvalidatepredcp0005cf\nverifymessage \"address\" \"signature\" \"message\"\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpubpassphrasechange \"oldpassphrase\" \"newpassphrase\"" diff --git a/internal/rpchelp/helpdescs_en_US.go b/internal/rpchelp/helpdescs_en_US.go index 796b838c9..0385c00df 100644 --- a/internal/rpchelp/helpdescs_en_US.go +++ b/internal/rpchelp/helpdescs_en_US.go @@ -711,6 +711,13 @@ var helpDescsEnUS = map[string]string{ "rescanwallet--synopsis": "Rescan the block chain for wallet data, blocking until the rescan completes or exits with an error", "rescanwallet-beginheight": "The height of the first block to begin the rescan from", + // RunAccountMixerCmd help. + "runaccountmixer--synopsis": "Starts a new account mixer for the specified account (and branch)", + "runaccountmixer-passphrase": "The private passphrase to unlock the wallet", + "runaccountmixer-mixedaccount": "The account number to which the mixing funds should end up", + "runaccountmixer-mixedaccountbranch": "The branch number to which the mixing funds should end up", + "runaccountmixer-changeaccount": "The account that will be used for any unmixed change that is waiting to be mixed", + // SendFromCmd help. "sendfrom--synopsis": "Authors, signs, and sends a transaction that outputs some amount to a payment address.\n" + "A change output is automatically included to send extra output value back to the original account.", diff --git a/internal/rpchelp/methods.go b/internal/rpchelp/methods.go index b7c8954f3..bfb20cfe5 100644 --- a/internal/rpchelp/methods.go +++ b/internal/rpchelp/methods.go @@ -96,6 +96,7 @@ var Methods = []struct { {"redeemmultisigouts", []any{(*types.RedeemMultiSigOutResult)(nil)}}, {"renameaccount", nil}, {"rescanwallet", nil}, + {"runaccountmixer", nil}, {"sendfrom", returnsString}, {"sendfromtreasury", returnsString}, {"sendmany", returnsString}, diff --git a/rpc/jsonrpc/types/methods.go b/rpc/jsonrpc/types/methods.go index 430ab1b06..b2ba9930c 100644 --- a/rpc/jsonrpc/types/methods.go +++ b/rpc/jsonrpc/types/methods.go @@ -1123,6 +1123,27 @@ func NewWalletPassphraseChangeCmd(oldPassphrase, newPassphrase string) *WalletPa } } +// RunAccountMixerCmd defines the runaccountmixer JSON-RPC command. +type RunAccountMixerCmd struct { + Passphrase string + MixedAccount uint32 + MixedAccountBranch uint32 + ChangeAccount uint32 +} + +// NewRunAccountMixerCmd returns a new instance which can be used to issue a +// runaccountmixer JSON-RPC command. +func NewRunAccountMixerCmd(passphrase string, mixedAccount uint32, + mixedAccountBranch uint32, changeAccount uint32, + csppServer string) *RunAccountMixerCmd { + return &RunAccountMixerCmd{ + Passphrase: passphrase, + MixedAccount: mixedAccount, + MixedAccountBranch: mixedAccountBranch, + ChangeAccount: changeAccount, + } +} + // MixAccountCmd defines the mixaccount JSON-RPC command. type MixAccountCmd struct{} @@ -1269,6 +1290,7 @@ func init() { {"renameaccount", (*RenameAccountCmd)(nil)}, {"rescanwallet", (*RescanWalletCmd)(nil)}, {"revoketickets", (*RevokeTicketsCmd)(nil)}, + {"runaccountmixer", (*RunAccountMixerCmd)(nil)}, {"sendfrom", (*SendFromCmd)(nil)}, {"sendfromtreasury", (*SendFromTreasuryCmd)(nil)}, {"sendmany", (*SendManyCmd)(nil)},