Skip to content

Commit

Permalink
client/rpcserver: Add cancel route
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeGruffins committed Jun 2, 2020
1 parent b90705e commit 5dd3c49
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 43 deletions.
1 change: 1 addition & 0 deletions client/cmd/dexcctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func main() {
// promptPasswords is a map of routes to password prompts. Passwords are
// prompted in the order given.
var promptPasswords = map[string][]string{
"cancel": {"App password:"},
"init": {"Set new app password:"},
"login": {"App password:"},
"newwallet": {"App password:", "Wallet password:"},
Expand Down
32 changes: 32 additions & 0 deletions client/rpcserver/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

// routes
const (
cancelRoute = "cancel"
closeWalletRoute = "closewallet"
exchangesRoute = "exchanges"
helpRoute = "help"
Expand All @@ -36,6 +37,7 @@ const (
walletCreatedStr = "%s wallet created and unlocked"
walletLockedStr = "%s wallet locked"
walletUnlockedStr = "%s wallet unlocked"
canceledOrderStr = "canceled order %s"
)

// createResponse creates a msgjson response payload.
Expand All @@ -59,6 +61,7 @@ func usage(route string, err error) *msgjson.ResponsePayload {

// routes maps routes to a handler function.
var routes = map[string]func(s *RPCServer, params *RawParams) *msgjson.ResponsePayload{
cancelRoute: handleCancel,
closeWalletRoute: handleCloseWallet,
exchangesRoute: handleExchanges,
helpRoute: handleHelp,
Expand Down Expand Up @@ -374,6 +377,24 @@ func handleTrade(s *RPCServer, params *RawParams) *msgjson.ResponsePayload {
return createResponse(tradeRoute, &tradeRes, nil)
}

// handleCancel handles requests for cancel. *msgjson.ResponsePayload.Error is
// empty if successful.
func handleCancel(s *RPCServer, params *RawParams) *msgjson.ResponsePayload {
form, err := parseCancelArgs(params)
if err != nil {
return usage(cancelRoute, err)
}
defer form.AppPass.Clear()
if err := s.core.Cancel(form.AppPass, form.OrderID); err != nil {
errMsg := fmt.Sprintf("unable to cancel order %q: %v", form.OrderID, err)
resErr := msgjson.NewError(msgjson.RPCCancelError, errMsg)
return createResponse(tradeRoute, nil, resErr)
}
resp := fmt.Sprintf(canceledOrderStr, form.OrderID)

return createResponse(cancelRoute, &resp, nil)
}

// format concatenates thing and tail. If thing is empty, returns an empty
// string.
func format(thing, tail string) string {
Expand Down Expand Up @@ -707,4 +728,15 @@ Registration is complete after the fee transaction has been confirmed.`,
Jan 1 1970.
}`,
},
cancelRoute: {
pwArgsShort: `"appPass"`,
argsShort: `"orderID"`,
cmdSummary: `Cancel an order.`,
pwArgsLong: `Password Args:
appPass (string): The DEX client password.`,
argsLong: `Args:
orderID (string): The hex ID of the order to cancel`,
returns: `Returns:
string: The message "` + fmt.Sprintf(canceledOrderStr, "[order ID]") + `"`,
},
}
35 changes: 35 additions & 0 deletions client/rpcserver/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,3 +682,38 @@ func TestHandleTrade(t *testing.T) {
}
}
}

func TestHandleCancel(t *testing.T) {
params := &RawParams{
PWArgs: []encode.PassBytes{encode.PassBytes("123")},
Args: []string{"fb94fe99e4e32200a341f0f1cb33f34a08ac23eedab636e8adb991fa76343e1e"},
}
tests := []struct {
name string
params *RawParams
cancelErr error
wantErrCode int
}{{
name: "ok",
params: params,
wantErrCode: -1,
}, {
name: "core.Cancel error",
params: params,
cancelErr: errors.New("error"),
wantErrCode: msgjson.RPCCancelError,
}, {
name: "bad params",
params: &RawParams{},
wantErrCode: msgjson.RPCArgumentsError,
}}
for _, test := range tests {
tc := &TCore{cancelErr: test.cancelErr}
r := &RPCServer{core: tc}
payload := handleCancel(r, test.params)
res := ""
if err := verifyResponse(payload, &res, test.wantErrCode); err != nil {
t.Fatal(err)
}
}
}
7 changes: 4 additions & 3 deletions client/rpcserver/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,17 @@ var (
// ClientCore is satisfied by core.Core.
type ClientCore interface {
AssetBalances(assetID uint32) (*core.BalanceSet, error)
Book(dex string, base, quote uint32) (orderBook *core.OrderBook, err error)
Book(host string, base, quote uint32) (orderBook *core.OrderBook, err error)
Cancel(appPass []byte, orderID string) error
CloseWallet(assetID uint32) error
CreateWallet(appPass, walletPass []byte, form *core.WalletForm) error
Exchanges() (exchanges map[string]*core.Exchange)
InitializeClient(appPass []byte) error
Login(appPass []byte) (*core.LoginResult, error)
OpenWallet(assetID uint32, pw []byte) error
OpenWallet(assetID uint32, appPass []byte) error
GetFee(addr, cert string) (fee uint64, err error)
Register(form *core.RegisterForm) error
Sync(dex string, base, quote uint32) (*core.OrderBook, *core.BookFeed, error)
Sync(host string, base, quote uint32) (*core.OrderBook, *core.BookFeed, error)
Trade(appPass []byte, form *core.TradeForm) (order *core.Order, err error)
WalletState(assetID uint32) (walletState *core.WalletState)
Wallets() (walletsStates []*core.WalletState)
Expand Down
7 changes: 7 additions & 0 deletions client/rpcserver/rpcserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,21 @@ type TCore struct {
loginResult *core.LoginResult
order *core.Order
tradeErr error
cancelErr error
}

func (c *TCore) Balance(uint32) (uint64, error) {
return 0, c.balanceErr
}
func (c *TCore) Book(dex string, base, quote uint32) (*core.OrderBook, error) {
return nil, nil
}
func (c *TCore) AssetBalances(uint32) (*core.BalanceSet, error) {
return nil, c.balanceErr
}
func (c *TCore) Cancel(pw []byte, sid string) error {
return c.cancelErr
}
func (c *TCore) CreateWallet(appPW, walletPW []byte, form *core.WalletForm) error {
return c.createWalletErr
}
Expand Down
28 changes: 27 additions & 1 deletion client/rpcserver/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
package rpcserver

import (
"encoding/hex"
"errors"
"fmt"
"strconv"

"decred.org/dcrdex/client/core"
"decred.org/dcrdex/dex/encode"
"decred.org/dcrdex/dex/order"
)

// An orderID is a 256 bit number encoded as a hex string.
const orderIdLen = 2 * order.OrderIDSize // 2 * 32

var (
// errArgs is wrapped when arguments to the known command cannot be parsed.
errArgs = errors.New("unable to parse arguments")
Expand Down Expand Up @@ -42,7 +47,7 @@ type getFeeResponse struct {

// tradeResponse is used when responding to the trade route.
type tradeResponse struct {
OrderID string `json:"orderid"`
OrderID string `json:"orderID"`
Sig string `json:"sig"`
Stamp uint64 `json:"stamp"`
}
Expand All @@ -68,11 +73,18 @@ type helpForm struct {
IncludePasswords bool `json:"includepasswords"`
}

// tradeForm combines the application password and the user's trade details.
type tradeForm struct {
AppPass encode.PassBytes
SrvForm *core.TradeForm
}

// cancelForm is information necessary to cancel a trade.
type cancelForm struct {
AppPass encode.PassBytes `json:"appPass"`
OrderID string `json:"orderID"`
}

// 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.
Expand Down Expand Up @@ -270,3 +282,17 @@ func parseTradeArgs(params *RawParams) (*tradeForm, error) {
}
return req, nil
}

func parseCancelArgs(params *RawParams) (*cancelForm, error) {
if err := checkNArgs(params, []int{1}, []int{1}); err != nil {
return nil, err
}
id := params.Args[0]
if len(id) != orderIdLen {
return nil, fmt.Errorf("%w: orderID has incorrect length", errArgs)
}
if _, err := hex.DecodeString(id); err != nil {
return nil, fmt.Errorf("%w: invalid order id hex", errArgs)
}
return &cancelForm{AppPass: params.PWArgs[0], OrderID: id}, nil
}
64 changes: 52 additions & 12 deletions client/rpcserver/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,14 @@ func TestCheckNArgs(t *testing.T) {
pwArgs[i] = encode.PassBytes(testValue)
}
err := checkNArgs(&RawParams{PWArgs: pwArgs, Args: test.have}, test.wantNArgs, test.wantNArgs)
if test.wantErr {
if err == nil {
t.Fatalf("expected error for test %s",
test.name)
if err != nil {
if test.wantErr {
continue
}
continue
t.Fatalf("unexpected error for test %s: %v", test.name, err)
}
if err != nil {
t.Fatalf("unexpected error for test %s: %v",
test.name, err)
if test.wantErr {
t.Fatalf("expected error for test %s", test.name)
}
}
}
Expand Down Expand Up @@ -99,7 +97,7 @@ func TestParseNewWalletArgs(t *testing.T) {
}}
for _, test := range tests {
nwf, err := parseNewWalletArgs(test.params)
if test.wantErr != nil {
if err != nil {
if !errors.Is(err, test.wantErr) {
t.Fatalf("unexpected error %v for test %s",
err, test.name)
Expand Down Expand Up @@ -145,7 +143,7 @@ func TestParseOpenWalletArgs(t *testing.T) {
}}
for _, test := range tests {
owf, err := parseOpenWalletArgs(test.params)
if test.wantErr != nil {
if err != nil {
if !errors.Is(err, test.wantErr) {
t.Fatalf("unexpected error %v for test %s",
err, test.name)
Expand All @@ -165,11 +163,13 @@ func TestCheckUIntArg(t *testing.T) {
tests := []struct {
name string
arg string
want uint64
bitSize int
wantErr error
}{{
name: "ok",
arg: "4294967295",
want: 4294967295,
bitSize: 32,
}, {
name: "too big",
Expand All @@ -196,8 +196,8 @@ func TestCheckUIntArg(t *testing.T) {
}
continue
}
if fmt.Sprint(res) != test.arg {
t.Fatalf("strings don't match for test %s", test.name)
if res != test.want {
t.Fatalf("expected %d but got %d for test %q", test.want, res, test.name)
}
}
}
Expand Down Expand Up @@ -425,3 +425,43 @@ func TestTradeArgs(t *testing.T) {
}
}
}

func TestParseCancelArgs(t *testing.T) {
paramsWithOrderID := func(orderID string) *RawParams {
pw := encode.PassBytes("password123")
pwArgs := []encode.PassBytes{pw}
return &RawParams{PWArgs: pwArgs, Args: []string{orderID}}
}
tests := []struct {
name string
params *RawParams
wantErr error
}{{
name: "ok",
params: paramsWithOrderID("fb94fe99e4e32200a341f0f1cb33f34a08ac23eedab636e8adb991fa76343e1e"),
}, {
name: "order ID incorrect length",
params: paramsWithOrderID("94fe99e4e32200a341f0f1cb33f34a08ac23eedab636e8adb991fa76343e1e"),
wantErr: errArgs,
}, {
name: "order ID not hex",
params: paramsWithOrderID("zb94fe99e4e32200a341f0f1cb33f34a08ac23eedab636e8adb991fa76343e1e"),
wantErr: errArgs,
}}
for _, test := range tests {
reg, err := parseCancelArgs(test.params)
if err != nil {
if !errors.Is(err, test.wantErr) {
t.Fatalf("unexpected error %v for test %q",
err, test.name)
}
continue
}
if !bytes.Equal(reg.AppPass, test.params.PWArgs[0]) {
t.Fatalf("appPass doesn't match")
}
if fmt.Sprint(reg.OrderID) != test.params.Args[0] {
t.Fatalf("order ID doesn't match")
}
}
}
55 changes: 28 additions & 27 deletions dex/msgjson/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,33 +31,34 @@ const (
RPCRegisterError // 15
RPCArgumentsError // 16
RPCTradeError // 17
SignatureError // 18
SerializationError // 19
TransactionUndiscovered // 20
ContractError // 21
SettlementSequenceError // 22
ResultLengthError // 23
IDMismatchError // 24
RedemptionError // 25
IDTypeError // 26
AckCountError // 27
UnknownResponseID // 28
OrderParameterError // 29
UnknownMarketError // 30
ClockRangeError // 31
FundingError // 32
CoinAuthError // 33
UnknownMarket // 34
NotSubscribedError // 35
UnauthorizedConnection // 36
AuthenticationError // 37
PubKeyParseError // 38
FeeError // 39
InvalidPreimage // 40
PreimageCommitmentMismatch // 41
UnknownMessageType // 42
AccountClosedError // 43
MarketNotRunningError // 44
RPCCancelError // 18
SignatureError // 19
SerializationError // 20
TransactionUndiscovered // 21
ContractError // 22
SettlementSequenceError // 23
ResultLengthError // 24
IDMismatchError // 25
RedemptionError // 26
IDTypeError // 27
AckCountError // 28
UnknownResponseID // 29
OrderParameterError // 30
UnknownMarketError // 31
ClockRangeError // 32
FundingError // 33
CoinAuthError // 34
UnknownMarket // 35
NotSubscribedError // 36
UnauthorizedConnection // 37
AuthenticationError // 38
PubKeyParseError // 39
FeeError // 40
InvalidPreimage // 41
PreimageCommitmentMismatch // 42
UnknownMessageType // 43
AccountClosedError // 44
MarketNotRunningError // 45
)

// Routes are destinations for a "payload" of data. The type of data being
Expand Down

0 comments on commit 5dd3c49

Please sign in to comment.