From 25a52a4119c86738aeb58f9909da03fc79eefff9 Mon Sep 17 00:00:00 2001 From: JoeGruff Date: Fri, 14 Apr 2023 16:12:32 +0900 Subject: [PATCH] client/dcr: Add rpc staking. --- client/asset/dcr/rpcwallet.go | 18 ++- client/cmd/dexcctl/main.go | 1 + client/core/core.go | 72 ++++++++++++ client/rpcserver/handlers.go | 170 +++++++++++++++++++++++++++-- client/rpcserver/handlers_test.go | 144 ++++++++++++++++++++++++ client/rpcserver/rpcserver.go | 6 + client/rpcserver/rpcserver_test.go | 18 +++ client/rpcserver/types.go | 68 ++++++++++++ client/rpcserver/types_test.go | 101 +++++++++++++++++ dex/msgjson/types.go | 4 + 10 files changed, 590 insertions(+), 12 deletions(-) diff --git a/client/asset/dcr/rpcwallet.go b/client/asset/dcr/rpcwallet.go index 6a8cb5a45e..14d0f420a1 100644 --- a/client/asset/dcr/rpcwallet.go +++ b/client/asset/dcr/rpcwallet.go @@ -912,18 +912,28 @@ func (w *rpcWallet) Tickets(ctx context.Context) ([]*asset.Ticket, error) { w.log.Errorf("GetTransaction error for ticket %s: %v", h, err) continue } + blkHash, err := chainhash.NewHashFromStr(tx.BlockHash) + if err != nil { + w.log.Errorf("Invalid block hash %v for ticket %v: %w", tx.BlockHash, h, err) + continue + } + // dcrwallet returns do not include the block height. + hdr, err := w.client().GetBlockHeader(ctx, blkHash) + if err != nil { + w.log.Errorf("GetBlockHeader error for ticket %s: %v", h, err) + continue + } msgTx, err := msgTxFromHex(tx.Hex) if err != nil { w.log.Errorf("Error decoding ticket %s tx hex: %v", h, err) continue } - if len(msgTx.TxOut) < 1 { w.log.Errorf("No outputs for ticket %s", h) continue } - - feeAmt, _ := dcrutil.NewAmount(tx.Fee) + // Fee is always negative. + feeAmt, _ := dcrutil.NewAmount(-tx.Fee) tickets = append(tickets, &asset.Ticket{ Ticket: asset.TicketTransaction{ @@ -931,7 +941,7 @@ func (w *rpcWallet) Tickets(ctx context.Context) ([]*asset.Ticket, error) { TicketPrice: uint64(msgTx.TxOut[0].Value), Fees: uint64(feeAmt), Stamp: uint64(tx.Time), - BlockHeight: tx.BlockIndex, + BlockHeight: int64(hdr.Height), }, // The walletjson.GetTransactionResult returned from GetTransaction // actually has a TicketStatus string field, but it doesn't appear diff --git a/client/cmd/dexcctl/main.go b/client/cmd/dexcctl/main.go index 77d22c908f..926b0ea251 100644 --- a/client/cmd/dexcctl/main.go +++ b/client/cmd/dexcctl/main.go @@ -60,6 +60,7 @@ var promptPasswords = map[string][]string{ "appseed": {"App password:"}, "startmarketmaking": {"App password:"}, "multitrade": {"App password:"}, + "purchasetickets": {"App password:"}, } // optionalTextFiles is a map of routes to arg index for routes that should read diff --git a/client/core/core.go b/client/core/core.go index 02eb462572..8318a8fd54 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -10498,3 +10498,75 @@ func (c *Core) SendShielded(appPW []byte, assetID uint32, toAddr string, amt uin return coinID, nil } + +// dcrStakingWallet fetches the decred wallet and returns its asset.TicketBuyer +// interface. Errors if no decred wallet is currently loaded. Used for decred +// ticket purchasing. +func (c *Core) dcrStakingWallet() (*xcWallet, asset.TicketBuyer, error) { + dcrAssetID := uint32(42) + wallet, exists := c.wallet(dcrAssetID) + if !exists { + return nil, nil, newError(missingWalletErr, "no configured wallet found for dcr") + } + ticketBuyer, is := wallet.Wallet.(asset.TicketBuyer) + if !is { + return nil, nil, fmt.Errorf("dcr wallet is not a TicketBuyer") + } + return wallet, ticketBuyer, nil +} + +// StakeStatus returns current staking statuses such as currently owned +// tickets, ticket price, and current voting preferences. Used for +// decred ticket purchasing. +func (c *Core) StakeStatus() (*asset.TicketStakingStatus, error) { + _, tb, err := c.dcrStakingWallet() + if err != nil { + return nil, err + } + return tb.StakeStatus() +} + +// SetVSP sets the VSP provider. Used for decred ticket purchasing. +func (c *Core) SetVSP(addr string) error { + _, tb, err := c.dcrStakingWallet() + if err != nil { + return err + } + return tb.SetVSP(addr) +} + +// PurchaseTickets purchases n amout of tickets. Returns the purchased +// ticket hashes if successful.Used for decred ticket purchasing. +func (c *Core) PurchaseTickets(pw []byte, n int) ([]string, error) { + wallet, tb, err := c.dcrStakingWallet() + if err != nil { + return nil, err + } + crypter, err := c.encryptionKey(pw) + if err != nil { + return nil, fmt.Errorf("password error: %w", err) + } + defer crypter.Close() + err = c.connectAndUnlock(crypter, wallet) + if err != nil { + return nil, err + } + + // TODO: Send tickets bought notification. + //subject, details := c.formatDetails(TopicSendSuccess, sentValue, unbip(assetID), address, coin) + //c.notify(newSendNote(TopicSendSuccess, subject, details, db.Success)) + + dcrAssetID := uint32(42) + c.updateAssetBalance(dcrAssetID) + return tb.PurchaseTickets(n) +} + +// SetVotingPreferences sets default voting settings for all active +// tickets and future tickets. Nil maps can be provided for no change. +func (c *Core) SetVotingPreferences(choices, tSpendPolicy, treasuryPolicy map[string]string) error { + _, tb, err := c.dcrStakingWallet() + if err != nil { + return err + } + return tb.SetVotingPreferences(choices, tSpendPolicy, treasuryPolicy) +} diff --git a/client/rpcserver/handlers.go b/client/rpcserver/handlers.go index 68e56aa85e..d65041ed6b 100644 --- a/client/rpcserver/handlers.go +++ b/client/rpcserver/handlers.go @@ -55,6 +55,10 @@ const ( startMarketMakingRoute = "startmarketmaking" stopMarketMakingRoute = "stopmarketmaking" multiTradeRoute = "multitrade" + stakeStatusRoute = "stakestatus" + setVSPRoute = "setvsp" + purchaseTicketsRoute = "purchasetickets" + setVotingPreferencesRoute = "setvotingprefs" ) const ( @@ -65,6 +69,8 @@ const ( canceledOrderStr = "canceled order %s" logoutStr = "goodbye" walletStatusStr = "%s wallet has been %s" + setVotePrefsStr = "vote preferences set" + setVSPStr = "vsp set to %s" ) // createResponse creates a msgjson response payload. @@ -116,10 +122,14 @@ var routes = map[string]func(s *RPCServer, params *RawParams) *msgjson.ResponseP walletPeersRoute: handleWalletPeers, addWalletPeerRoute: handleAddWalletPeer, removeWalletPeerRoute: handleRemoveWalletPeer, - notificationsRoute: handleNotificationsRoute, - startMarketMakingRoute: handleStartMarketMakingRoute, - stopMarketMakingRoute: handleStopMarketMakingRoute, + notificationsRoute: handleNotifications, + startMarketMakingRoute: handleStartMarketMaking, + stopMarketMakingRoute: handleStopMarketMaking, multiTradeRoute: handleMultiTrade, + stakeStatusRoute: handleStakeStatus, + setVSPRoute: handleSetVSP, + purchaseTicketsRoute: handlePurchaseTickets, + setVotingPreferencesRoute: handleSetVotingPreferences, } // handleHelp handles requests for help. Returns general help for all commands @@ -888,7 +898,7 @@ func handleRemoveWalletPeer(s *RPCServer, params *RawParams) *msgjson.ResponsePa return createResponse(removeWalletPeerRoute, "successfully removed peer", nil) } -func handleNotificationsRoute(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { +func handleNotifications(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { numNotes, err := parseNotificationsArgs(params) if err != nil { return usage(notificationsRoute, err) @@ -896,7 +906,7 @@ func handleNotificationsRoute(s *RPCServer, params *RawParams) *msgjson.Response notes, err := s.core.Notifications(numNotes) if err != nil { - errMsg := fmt.Sprintf("unable to remove wallet peer: %v", err) + errMsg := fmt.Sprintf("unable to handle notification: %v", err) resErr := msgjson.NewError(msgjson.RPCNotificationsError, errMsg) return createResponse(notificationsRoute, nil, resErr) } @@ -921,7 +931,7 @@ func parseMarketMakingConfig(path string) ([]*mm.BotConfig, error) { return configs, nil } -func handleStartMarketMakingRoute(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { +func handleStartMarketMaking(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { form, err := parseStartMarketMakingArgs(params) if err != nil { return usage(startMarketMakingRoute, err) @@ -944,7 +954,7 @@ func handleStartMarketMakingRoute(s *RPCServer, params *RawParams) *msgjson.Resp return createResponse(startMarketMakingRoute, "started market making", nil) } -func handleStopMarketMakingRoute(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { +func handleStopMarketMaking(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { if !s.mm.Running() { errMsg := "market making is not running" resErr := msgjson.NewError(msgjson.RPCStopMarketMakingError, errMsg) @@ -954,6 +964,66 @@ func handleStopMarketMakingRoute(s *RPCServer, params *RawParams) *msgjson.Respo return createResponse(stopMarketMakingRoute, "stopped market making", nil) } +func handleSetVSP(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { + addr, err := parseSetVSPArgs(params) + if err != nil { + return usage(setVSPRoute, err) + } + + err = s.core.SetVSP(addr) + if err != nil { + errMsg := fmt.Sprintf("unable to set vsp: %v", err) + resErr := msgjson.NewError(msgjson.RPCSetVSPError, errMsg) + return createResponse(setVSPRoute, nil, resErr) + } + + return createResponse(setVSPRoute, fmt.Sprintf(setVSPStr, addr), nil) +} + +func handlePurchaseTickets(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { + form, err := parsePurchaseTicketsArgs(params) + if err != nil { + return usage(purchaseTicketsRoute, err) + } + defer form.appPass.Clear() + + hashes, err := s.core.PurchaseTickets(form.appPass, form.num) + if err != nil { + errMsg := fmt.Sprintf("unable to purchase tickets: %v", err) + resErr := msgjson.NewError(msgjson.RPCPurchaseTicketsError, errMsg) + return createResponse(purchaseTicketsRoute, nil, resErr) + } + + return createResponse(purchaseTicketsRoute, hashes, nil) +} + +func handleStakeStatus(s *RPCServer, _ *RawParams) *msgjson.ResponsePayload { + stakeStatus, err := s.core.StakeStatus() + if err != nil { + errMsg := fmt.Sprintf("unable to get staking status: %v", err) + resErr := msgjson.NewError(msgjson.RPCStakeStatusError, errMsg) + return createResponse(stakeStatusRoute, nil, resErr) + } + + return createResponse(stakeStatusRoute, &stakeStatus, nil) +} + +func handleSetVotingPreferences(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { + form, err := parseSetVotingPreferencesArgs(params) + if err != nil { + return usage(setVotingPreferencesRoute, err) + } + + err = s.core.SetVotingPreferences(form.voteChoices, form.tSpendPolicy, form.treasuryPolicy) + if err != nil { + errMsg := fmt.Sprintf("unable to set voting preferences: %v", err) + resErr := msgjson.NewError(msgjson.RPCSetVotingPreferencesError, errMsg) + return createResponse(setVotingPreferencesRoute, nil, resErr) + } + + return createResponse(setVotingPreferencesRoute, "vote preferences set", nil) +} + // format concatenates thing and tail. If thing is empty, returns an empty // string. func format(thing, tail string) string { @@ -1385,7 +1455,7 @@ Registration is complete after the fee transaction has been confirmed.`, cmdSummary: `Initiate a rescan of an asset's wallet. This is only supported for certain wallet types. Wallet resynchronization may be asynchronous, and the wallet state should be consulted for progress. - + WARNING: It is ill-advised to initiate a wallet rescan with active orders unless as a last ditch effort to get the wallet to recognize a transaction needed to complete a swap.`, @@ -1584,4 +1654,88 @@ needed to complete a swap.`, stopMarketMakingRoute: { cmdSummary: `Stop market making.`, }, + stakeStatusRoute: { + cmdSummary: `Get stake status. `, + returns: `Returns: + obj: The staking status. + { + ticketprice (uint64): The current ticket price in atoms. + vsp (string): The url of the currently set vsp (voting service provider). + isrpc (bool): Whether the wallet is an RPC wallet. False indicates +an spv wallet and allows options that can view and set the vsp. + tickets (array): An array of ticket objects. + [ + { + ticket (obj): Ticket transaction data. + { + hash (string): The ticket hash as hex. + ticketPrice (int): The amount paid for the ticket in atoms. + fees (int): The ticket transaction's tx fee. + stamp (int): The UNIX time the ticket was purchased. + blockheight (int): The block number the ticket was mined. + }, + status: (int) The ticket status. 0 is TicketStatusUnknown. 1 is TicketStatusUnmined. +2 is TicketStatusImmature. 3 is TicketStatusLive. 4 is TicketStatusVoted. +5 is TicketStatusMissed. 6 is TicketStatusExpired. 7 is TicketStatusUnspent. +8 is TicketStatusRevoked. + spender (string): The transaction that votes on or revokes the ticket if available. + }, + ],... + stances (obj): Voting policies. + { + votechoices (array): An array of consensus vote choices. + [ + { + agendaid (string): The agenda ID, + agendadescription (string): A description of the agenda being voted on. + choiceid (string): The current choice. + choicedescription (string): A description of the chosen choice. + }, + ],... + tspendpolicy (array): An array of TSpend policies. + [ + { + hash (string): The TSpend txid., + policy (string): The policy. + }, + ],... + treasurypolicy (array): An array of treasury policies. + [ + { + key (string): The pubkey of the tspend creator. + policy (string): The policy. + }, + ],... + } + }`, + }, + setVSPRoute: { + argsShort: `"addr"`, + cmdSummary: `Set a vsp by url.`, + argsLong: `Args: + addr (string): The vsp's url.`, + returns: `Returns: + string: The message "` + fmt.Sprintf(setVSPStr, "[vsp url]") + `"`, + }, + purchaseTicketsRoute: { + pwArgsShort: `"appPass"`, + argsShort: `num`, + cmdSummary: `Purchase some tickets.`, + pwArgsLong: `Password Args: + appPass (string): The DEX client password.`, + argsLong: `Args: + num (int): The number of tickets to purchase`, + returns: `Returns: + array: An array of ticket hashes.`, + }, + setVotingPreferencesRoute: { + argsShort: `(choicesMap) (tSpendPolicyMap) (treasuryPolicyMap)`, + cmdSummary: `Cancel an order.`, + argsLong: `Args: + choicesMap (map[string]string): A map of choices IDs to choice policies. + tSpendPolicyMap (map[string]string): A map of tSpend txids to tSpend policies. + treasuryPolicyMap (map[string]string): A map of treasury spender public keys to tSpend policies.`, + returns: `Returns: + string: The message "` + setVotePrefsStr + `"`, + }, } diff --git a/client/rpcserver/handlers_test.go b/client/rpcserver/handlers_test.go index 12278d7699..fa405f8310 100644 --- a/client/rpcserver/handlers_test.go +++ b/client/rpcserver/handlers_test.go @@ -1300,3 +1300,147 @@ func TestDeleteRecords(t *testing.T) { } } } + +func TestHandleSetVSP(t *testing.T) { + params := &RawParams{ + Args: []string{ + "url.com", + }, + } + tests := []struct { + name string + params *RawParams + setVSPErr error + wantErrCode int + }{{ + name: "ok", + params: params, + wantErrCode: -1, + }, { + name: "core.SetVSP error", + params: params, + setVSPErr: errors.New("error"), + wantErrCode: msgjson.RPCSetVSPError, + }, { + name: "bad params", + params: &RawParams{}, + wantErrCode: msgjson.RPCArgumentsError, + }} + for _, test := range tests { + tc := &TCore{setVSPErr: test.setVSPErr} + r := &RPCServer{core: tc} + payload := handleSetVSP(r, test.params) + res := "" + if err := verifyResponse(payload, &res, test.wantErrCode); err != nil { + t.Fatal(err) + } + } +} + +func TestPurchaseTickets(t *testing.T) { + pw := encode.PassBytes("password123") + params := &RawParams{ + PWArgs: []encode.PassBytes{pw}, + Args: []string{ + "42", + }, + } + tickets := []string{"txidA", "txidB"} + tests := []struct { + name string + params *RawParams + purchaseTicketsErr error + purchaseTickets []string + wantErrCode int + }{{ + name: "ok", + params: params, + purchaseTickets: tickets, + wantErrCode: -1, + }, { + name: "core.PurchaseTickets error", + params: params, + purchaseTicketsErr: errors.New("error"), + wantErrCode: msgjson.RPCPurchaseTicketsError, + }, { + name: "bad params", + params: &RawParams{}, + wantErrCode: msgjson.RPCArgumentsError, + }} + for _, test := range tests { + tc := &TCore{ + purchseTickets: test.purchaseTickets, + purchaseTicketsErr: test.purchaseTicketsErr, + } + r := &RPCServer{core: tc} + payload := handlePurchaseTickets(r, test.params) + res := new([]string) + if err := verifyResponse(payload, &res, test.wantErrCode); err != nil { + t.Fatal(err) + } + if test.wantErrCode == -1 && len(*res) != 2 { + t.Fatalf("expected two tickets but got %d", len(*res)) + } + } +} + +func TestHandleStakeStatus(t *testing.T) { + tests := []struct { + name string + params *RawParams + stakeStatusErr error + wantErrCode int + }{{ + name: "ok", + wantErrCode: -1, + }, { + name: "core.StakeStatus error", + stakeStatusErr: errors.New("error"), + wantErrCode: msgjson.RPCStakeStatusError, + }} + for _, test := range tests { + tc := &TCore{stakeStatusErr: test.stakeStatusErr} + r := &RPCServer{core: tc} + payload := handleStakeStatus(r, test.params) + res := new(asset.TicketStakingStatus) + if err := verifyResponse(payload, &res, test.wantErrCode); err != nil { + t.Fatal(err) + } + } +} + +func TestHandleSetVotingPreferences(t *testing.T) { + params := &RawParams{} + tests := []struct { + name string + params *RawParams + setVotingPrefErr error + wantErrCode int + }{{ + name: "ok", + params: params, + wantErrCode: -1, + }, { + name: "core.SetVotingPreferences error", + params: params, + setVotingPrefErr: errors.New("error"), + wantErrCode: msgjson.RPCSetVotingPreferencesError, + }, { + name: "bad params", + params: &RawParams{ + Args: []string{ + "asdf", + }, + }, + wantErrCode: msgjson.RPCArgumentsError, + }} + for _, test := range tests { + tc := &TCore{setVotingPrefErr: test.setVotingPrefErr} + r := &RPCServer{core: tc} + payload := handleSetVotingPreferences(r, test.params) + res := "" + if err := verifyResponse(payload, &res, test.wantErrCode); err != nil { + t.Fatal(err) + } + } +} diff --git a/client/rpcserver/rpcserver.go b/client/rpcserver/rpcserver.go index 91037bec59..cf6b3723c5 100644 --- a/client/rpcserver/rpcserver.go +++ b/client/rpcserver/rpcserver.go @@ -85,6 +85,12 @@ type clientCore interface { RemoveWalletPeer(assetID uint32, host string) error Notifications(int) ([]*db.Notification, error) MultiTrade(pw []byte, form *core.MultiTradeForm) ([]*core.Order, error) + + // These are core's ticket buying interface. + StakeStatus() (*asset.TicketStakingStatus, error) + SetVSP(addr string) error + PurchaseTickets(pw []byte, n int) ([]string, error) + SetVotingPreferences(choices, tSpendPolicy, treasuryPolicy map[string]string) error } // RPCServer is a single-client http and websocket server enabling a JSON diff --git a/client/rpcserver/rpcserver_test.go b/client/rpcserver/rpcserver_test.go index 526ef5bf9e..db8b85da68 100644 --- a/client/rpcserver/rpcserver_test.go +++ b/client/rpcserver/rpcserver_test.go @@ -67,6 +67,12 @@ type TCore struct { discoverAcctErr error archivedRecords int deleteArchivedRecordsErr error + setVSPErr error + purchseTickets []string + purchaseTicketsErr error + stakeStatus *asset.TicketStakingStatus + stakeStatusErr error + setVotingPrefErr error } func (c *TCore) Balance(uint32) (uint64, error) { @@ -175,6 +181,18 @@ func (c *TCore) Notifications(n int) ([]*db.Notification, error) { func (c *TCore) MultiTrade(appPass []byte, form *core.MultiTradeForm) ([]*core.Order, error) { return nil, nil } +func (c *TCore) SetVSP(addr string) error { + return c.setVSPErr +} +func (c *TCore) PurchaseTickets(pw []byte, n int) ([]string, error) { + return c.purchseTickets, c.purchaseTicketsErr +} +func (c *TCore) StakeStatus() (*asset.TicketStakingStatus, error) { + return c.stakeStatus, c.stakeStatusErr +} +func (c *TCore) SetVotingPreferences(choices, tSpendPolicy, treasuryPolicy map[string]string) error { + return c.setVotingPrefErr +} type tBookFeed struct{} diff --git a/client/rpcserver/types.go b/client/rpcserver/types.go index b9e8639e8a..6fe6c128c2 100644 --- a/client/rpcserver/types.go +++ b/client/rpcserver/types.go @@ -197,6 +197,15 @@ type startMarketMakingForm struct { cfgFilePath string } +type purchaseTicketsForm struct { + num int + appPass encode.PassBytes +} + +type setVotingPreferencesForm struct { + voteChoices, tSpendPolicy, treasuryPolicy map[string]string +} + // checkNArgs checks that args and pwArgs are the correct length. func checkNArgs(params *RawParams, nPWArgs, nArgs []int) error { // For want, one integer indicates an exact match, two are the min and max. @@ -836,3 +845,62 @@ func parseStartMarketMakingArgs(params *RawParams) (*startMarketMakingForm, erro form.cfgFilePath = params.Args[0] return form, nil } + +func parseSetVSPArgs(params *RawParams) (addr string, err error) { + if err := checkNArgs(params, []int{0}, []int{1}); err != nil { + return "", err + } + return params.Args[0], nil +} + +func parsePurchaseTicketsArgs(params *RawParams) (*purchaseTicketsForm, error) { + if err := checkNArgs(params, []int{1}, []int{1}); err != nil { + return nil, err + } + num, err := checkUIntArg(params.Args[0], "num", 32) + if err != nil { + return nil, fmt.Errorf("invalid num: %v", err) + } + return &purchaseTicketsForm{ + num: int(num), + appPass: params.PWArgs[0], + }, nil +} + +func parseSetVotingPreferencesArgs(params *RawParams) (*setVotingPreferencesForm, error) { + err := checkNArgs(params, []int{0}, []int{0, 3}) + if err != nil { + return nil, err + } + var p string + form := new(setVotingPreferencesForm) + switch len(params.Args) { + case 3: + p = params.Args[2] + if p != "" { + form.treasuryPolicy, err = checkMapArg(p, "treasury policy") + if err != nil { + return nil, err + } + } + fallthrough + case 2: + p = params.Args[1] + if p != "" { + form.tSpendPolicy, err = checkMapArg(p, "tspend policy") + if err != nil { + return nil, err + } + } + fallthrough + case 1: + p = params.Args[0] + if p != "" { + form.voteChoices, err = checkMapArg(p, "vote choices") + if err != nil { + return nil, err + } + } + } + return form, nil +} diff --git a/client/rpcserver/types_test.go b/client/rpcserver/types_test.go index 8ee6d3f053..c0dae809f5 100644 --- a/client/rpcserver/types_test.go +++ b/client/rpcserver/types_test.go @@ -811,3 +811,104 @@ func TestDeleteArchivedRecordsArgs(t *testing.T) { } } } + +func TestParseSetVSPArgs(t *testing.T) { + paramsWithArgs := func(args ...string) *RawParams { + return &RawParams{Args: args} + } + tests := []struct { + name string + params *RawParams + wantErr error + }{{ + name: "ok", + params: paramsWithArgs("asdf"), + }} + for _, test := range tests { + _, err := parseSetVSPArgs(test.params) + if test.wantErr != nil { + if err != nil { + continue + } + t.Fatalf("%q: expected error", test.name) + } + if err != nil { + t.Fatalf("%q: unexpected error: %v", test.name, err) + } + } +} + +func TestPurchaseTicketsArgs(t *testing.T) { + pw := encode.PassBytes("password123") + pwArgs := []encode.PassBytes{pw} + paramsWithArgs := func(args ...string) *RawParams { + return &RawParams{Args: args, PWArgs: pwArgs} + } + tests := []struct { + name string + params *RawParams + wantErr error + }{{ + name: "ok", + params: paramsWithArgs("4"), + }, { + name: "num of tickets not a number", + params: paramsWithArgs("abc"), + wantErr: errArgs, + }} + for _, test := range tests { + _, err := parsePurchaseTicketsArgs(test.params) + if test.wantErr != nil { + if err != nil { + continue + } + t.Fatalf("%q: expected error", test.name) + } + if err != nil { + t.Fatalf("%q: unexpected error: %v", test.name, err) + } + } +} + +func TestParseSetVotingPreferencesArgs(t *testing.T) { + paramsWithArgs := func(args ...string) *RawParams { + return &RawParams{Args: args} + } + aMap := `{"txidA":"yes","txidB":"no"}` + tests := []struct { + name string + params *RawParams + wantErr error + }{{ + name: "ok no args", + params: paramsWithArgs(), + }, { + name: "ok three args", + params: paramsWithArgs(aMap, aMap, aMap), + }, { + name: "ok two args", + params: paramsWithArgs(aMap, aMap), + }, { + name: "ok one arg", + params: paramsWithArgs(aMap), + }, { + name: "ok blank strings", + params: paramsWithArgs("", "", aMap), + }, { + name: "bad map", + params: paramsWithArgs("asdf"), + wantErr: errArgs, + }} + for _, test := range tests { + _, err := parseSetVotingPreferencesArgs(test.params) + if test.wantErr != nil { + if err != nil { + continue + } + t.Fatalf("%q: expected error", test.name) + } + if err != nil { + t.Fatalf("%q: unexpected error: %v", test.name, err) + } + } +} diff --git a/dex/msgjson/types.go b/dex/msgjson/types.go index 0239eb7158..fc6fb5089a 100644 --- a/dex/msgjson/types.go +++ b/dex/msgjson/types.go @@ -89,6 +89,10 @@ const ( RPCWalletDefinitionError // 71 RPCStartMarketMakingError // 72 RPCStopMarketMakingError // 73 + RPCSetVSPError // 74 + RPCPurchaseTicketsError // 75 + RPCStakeStatusError // 76 + RPCSetVotingPreferencesError // 77 ) // Routes are destinations for a "payload" of data. The type of data being