From 558c54a8812ff2e78dbcc15fb3be0764f0f5a9e5 Mon Sep 17 00:00:00 2001 From: buck54321 Date: Mon, 15 Jun 2020 14:26:20 -0500 Subject: [PATCH] core: fix register test Fix the client/core TestRegister test function, which was timing out. Adds coded errors that can be used for ID. Improved Core.Register coverage. TestGetFee, with the coded errors. --- client/core/core.go | 32 ++--- client/core/core_test.go | 299 ++++++++++++++++++++++++--------------- client/core/errors.go | 58 ++++++++ 3 files changed, 258 insertions(+), 131 deletions(-) create mode 100644 client/core/errors.go diff --git a/client/core/core.go b/client/core/core.go index 4bcfcf43c5..1876081915 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -978,14 +978,14 @@ func (c *Core) isRegistered(host string) bool { func (c *Core) GetFee(dexAddr, cert string) (uint64, error) { host := addrHost(dexAddr) if c.isRegistered(host) { - return 0, fmt.Errorf("already registered at %s", dexAddr) + return 0, newError(dupeDEXErr, "already registered at %s", dexAddr) } dc, err := c.connectDEX(&db.AccountInfo{ Host: host, Cert: []byte(cert), }) if err != nil { - return 0, err + return 0, codedError(connectionErr, err) } defer dc.connMaster.Disconnect() return dc.cfg.Fee, nil @@ -1001,26 +1001,26 @@ func (c *Core) Register(form *RegisterForm) (*RegisterResult, error) { // Check the app password. crypter, err := c.encryptionKey(form.AppPass) if err != nil { - return nil, err + return nil, codedError(passwordErr, err) } if form.Addr == "" { - return nil, fmt.Errorf("no dex address specified") + return nil, newError(emptyHostErr, "no dex address specified") } host := addrHost(form.Addr) if c.isRegistered(host) { - return nil, fmt.Errorf("already registered at %s", form.Addr) + return nil, newError(dupeDEXErr, "already registered at %s", form.Addr) } regFeeAssetID, _ := dex.BipSymbolID(regFeeAssetSymbol) wallet, err := c.connectedWallet(regFeeAssetID) if err != nil { - return nil, fmt.Errorf("cannot connect to %s wallet to pay fee: %v", regFeeAssetSymbol, err) + return nil, newError(walletErr, "cannot connect to %s wallet to pay fee: %v", regFeeAssetSymbol, err) } if !wallet.unlocked() { err = unlockWallet(wallet, crypter) if err != nil { - return nil, fmt.Errorf("failed to unlock %s wallet: %v", unbip(wallet.AssetID), err) + return nil, newError(walletAuthErr, "failed to unlock %s wallet: %v", unbip(wallet.AssetID), err) } } @@ -1029,7 +1029,7 @@ func (c *Core) Register(form *RegisterForm) (*RegisterResult, error) { Cert: []byte(form.Cert), }) if err != nil { - return nil, err + return nil, codedError(connectionErr, err) } // close the connection to the dex server if the registration fails. @@ -1042,12 +1042,12 @@ func (c *Core) Register(form *RegisterForm) (*RegisterResult, error) { regAsset, found := dc.assets[regFeeAssetID] if !found { - return nil, fmt.Errorf("dex server does not support %s asset", regFeeAssetSymbol) + return nil, newError(assetSupportErr, "dex server does not support %s asset", regFeeAssetSymbol) } privKey, err := dc.acct.setupEncryption(crypter) if err != nil { - return nil, err + return nil, codedError(acctKeyErr, err) } // Prepare and sign the registration payload. @@ -1059,25 +1059,25 @@ func (c *Core) Register(form *RegisterForm) (*RegisterResult, error) { regRes := new(msgjson.RegisterResult) err = dc.signAndRequest(dexReg, msgjson.RegisterRoute, regRes) if err != nil { - return nil, err + return nil, codedError(registerErr, err) } // Check the DEX server's signature. msg := regRes.Serialize() dexPubKey, err := checkSigS256(msg, regRes.DEXPubKey, regRes.Sig) if err != nil { - return nil, fmt.Errorf("DEX signature validation error: %v", err) + return nil, newError(signatureErr, "DEX signature validation error: %v", err) } // Check that the fee is non-zero. if regRes.Fee == 0 { - return nil, fmt.Errorf("zero registration fees not allowed") + return nil, newError(zeroFeeErr, "zero registration fees not allowed") } if regRes.Fee != dc.cfg.Fee { - return nil, fmt.Errorf("DEX 'register' result fee doesn't match the 'config' value. %d != %d", regRes.Fee, dc.cfg.Fee) + return nil, newError(feeMismatchErr, "DEX 'register' result fee doesn't match the 'config' value. %d != %d", regRes.Fee, dc.cfg.Fee) } if regRes.Fee != form.Fee { - return nil, fmt.Errorf("registration fee provided to Register does not match the DEX registration fee. %d != %d", form.Fee, regRes.Fee) + return nil, newError(feeMismatchErr, "registration fee provided to Register does not match the DEX registration fee. %d != %d", form.Fee, regRes.Fee) } // Pay the registration fee. @@ -1085,7 +1085,7 @@ func (c *Core) Register(form *RegisterForm) (*RegisterResult, error) { regRes.Fee, regAsset.Symbol) coin, err := wallet.PayFee(regRes.Address, regRes.Fee, regAsset) if err != nil { - return nil, fmt.Errorf("error paying registration fee: %v", err) + return nil, newError(feeSendErr, "error paying registration fee: %v", err) } // Registration complete. diff --git a/client/core/core_test.go b/client/core/core_test.go index 3ce4de9f81..bc2394dc7a 100644 --- a/client/core/core_test.go +++ b/client/core/core_test.go @@ -56,14 +56,15 @@ var ( SwapConf: 1, FundConf: 1, } - tDexPriv *secp256k1.PrivateKey - tDexKey *secp256k1.PublicKey - tPW = []byte("dexpw") - wPW = "walletpw" - tDexHost = "somedex.tld" - tDcrBtcMktName = "dcr_btc" - tErr = fmt.Errorf("test error") - tFee uint64 = 1e8 + tDexPriv *secp256k1.PrivateKey + tDexKey *secp256k1.PublicKey + tPW = []byte("dexpw") + wPW = "walletpw" + tDexHost = "somedex.tld" + tDcrBtcMktName = "dcr_btc" + tErr = fmt.Errorf("test error") + tFee uint64 = 1e8 + tUnparseableHost = string([]byte{0x7f}) ) type tMsg = *msgjson.Message @@ -628,6 +629,45 @@ func newTestRig() *testRig { } } +func (rig *testRig) queueConfig() { + rig.ws.queueResponse(msgjson.ConfigRoute, func(msg *msgjson.Message, f msgFunc) error { + resp, _ := msgjson.NewResponse(msg.ID, rig.dc.cfg, nil) + f(resp) + return nil + }) +} + +func (rig *testRig) queueRegister(regRes *msgjson.RegisterResult) { + rig.ws.queueResponse(msgjson.RegisterRoute, func(msg *msgjson.Message, f msgFunc) error { + resp, _ := msgjson.NewResponse(msg.ID, regRes, nil) + f(resp) + return nil + }) +} + +func (rig *testRig) queueNotifyFee() { + rig.ws.queueResponse(msgjson.NotifyFeeRoute, func(msg *msgjson.Message, f msgFunc) error { + req := new(msgjson.NotifyFee) + json.Unmarshal(msg.Payload, req) + sigMsg := req.Serialize() + sig, _ := tDexPriv.Sign(sigMsg) + // Shouldn't Sig be dex.Bytes? + result := &msgjson.Acknowledgement{Sig: sig.Serialize()} + resp, _ := msgjson.NewResponse(msg.ID, result, nil) + f(resp) + return nil + }) +} + +func (rig *testRig) queueConnect() { + rig.ws.queueResponse(msgjson.ConnectRoute, func(msg *msgjson.Message, f msgFunc) error { + result := &msgjson.ConnectResult{} + resp, _ := msgjson.NewResponse(msg.ID, result, nil) + f(resp) + return nil + }) +} + func tMarketID(base, quote uint32) string { return strconv.Itoa(int(base)) + "-" + strconv.Itoa(int(quote)) } @@ -997,13 +1037,44 @@ func TestCreateWallet(t *testing.T) { } } +func TestGetFee(t *testing.T) { + rig := newTestRig() + tCore := rig.core + + // DEX already registered + _, err := tCore.GetFee(tDexHost, "") + if !errorHasCode(err, dupeDEXErr) { + t.Fatalf("wrong account exists error: %v", err) + } + + // Lose the dexConnection + tCore.connMtx.Lock() + delete(tCore.conns, tDexHost) + tCore.connMtx.Unlock() + + // connectDEX error + _, err = tCore.GetFee(tUnparseableHost, "") + if !errorHasCode(err, connectionErr) { + t.Fatalf("wrong connectDEX error: %v", err) + } + + // Queue a config response for success + rig.queueConfig() + + // Success + _, err = tCore.GetFee(tDexHost, "") + if err != nil { + t.Fatalf("GetFee error: %v", err) + } +} + func TestRegister(t *testing.T) { // This test takes a little longer because the key is decrypted every time // Register is called. rig := newTestRig() tCore := rig.core dc := rig.dc - acct := dc.acct + delete(tCore.conns, tDexHost) wallet, tWallet := newTWallet(tDCR.ID) tCore.wallets[tDCR.ID] = wallet @@ -1013,7 +1084,7 @@ func TestRegister(t *testing.T) { rig.db.acctErr = tErr regRes := &msgjson.RegisterResult{ - DEXPubKey: acct.dexPubKey.Serialize(), + DEXPubKey: rig.acct.dexPubKey.Serialize(), ClientPubKey: dex.Bytes{0x1}, // part of the serialization, but not the response Address: "someaddr", Fee: tFee, @@ -1021,33 +1092,6 @@ func TestRegister(t *testing.T) { } sign(tDexPriv, regRes) - queueRegister := func() { - rig.ws.queueResponse(msgjson.ConfigRoute, func(msg *msgjson.Message, f msgFunc) error { - resp, _ := msgjson.NewResponse(msg.ID, dc.cfg, nil) - f(resp) - return nil - }) - rig.ws.queueResponse(msgjson.RegisterRoute, func(msg *msgjson.Message, f msgFunc) error { - resp, _ := msgjson.NewResponse(msg.ID, regRes, nil) - f(resp) - return nil - }) - } - - queueNotifyFee := func() { - rig.ws.queueResponse(msgjson.NotifyFeeRoute, func(msg *msgjson.Message, f msgFunc) error { - req := new(msgjson.NotifyFee) - json.Unmarshal(msg.Payload, req) - sigMsg := req.Serialize() - sig, _ := tDexPriv.Sign(sigMsg) - // Shouldn't Sig be dex.Bytes? - result := &msgjson.Acknowledgement{Sig: sig.Serialize()} - resp, _ := msgjson.NewResponse(msg.ID, result, nil) - f(resp) - return nil - }) - } - queueTipChange := func() { go func() { timeout := time.NewTimer(time.Second * 2) @@ -1069,20 +1113,12 @@ func TestRegister(t *testing.T) { }() } - queueConnect := func() { - rig.ws.queueResponse(msgjson.ConnectRoute, func(msg *msgjson.Message, f msgFunc) error { - result := &msgjson.ConnectResult{} - resp, _ := msgjson.NewResponse(msg.ID, result, nil) - f(resp) - return nil - }) - } - queueResponses := func() { - queueRegister() + rig.queueConfig() + rig.queueRegister(regRes) queueTipChange() - queueNotifyFee() - queueConnect() + rig.queueNotifyFee() + rig.queueConnect() } form := &RegisterForm{ @@ -1152,89 +1188,142 @@ func TestRegister(t *testing.T) { t.Fatalf("fee payment error notification: %s: %s", feeNote.Subject(), feeNote.Details()) } + // password error + rig.crypter.recryptErr = tErr + _, err = tCore.Register(form) + if !errorHasCode(err, passwordErr) { + t.Fatalf("wrong password error: %v", err) + } + rig.crypter.recryptErr = nil + + // no host error + form.Addr = "" + _, err = tCore.Register(form) + if !errorHasCode(err, emptyHostErr) { + t.Fatalf("wrong empty host error: %v", err) + } + form.Addr = tDexHost + + // account already exists + tCore.connMtx.Lock() + tCore.conns[tDexHost] = dc + tCore.connMtx.Unlock() + _, err = tCore.Register(form) + if !errorHasCode(err, dupeDEXErr) { + t.Fatalf("wrong account exists error: %v", err) + } + // wallet not found delete(tCore.wallets, tDCR.ID) run() - if err == nil { - t.Fatalf("no error for missing wallet") + if !errorHasCode(err, walletErr) { + t.Fatalf("wrong missing wallet error: %v", err) } tCore.wallets[tDCR.ID] = wallet - // account already exists - rig.db.acct = &db.AccountInfo{ - Host: tDexHost, - EncKey: acct.encKey, - DEXPubKey: acct.dexPubKey, - FeeCoin: acct.feeCoin, + // Unlock wallet error + tWallet.unlockErr = tErr + wallet.lockTime = time.Time{} + _, err = tCore.Register(form) + if !errorHasCode(err, walletAuthErr) { + t.Fatalf("wrong wallet auth error: %v", err) } - rig.db.acctErr = nil - run() - if err == nil { - t.Fatalf("no error for account already exists") + tWallet.unlockErr = nil + + // connectDEX error + form.Addr = tUnparseableHost + _, err = tCore.Register(form) + if !errorHasCode(err, connectionErr) { + t.Fatalf("wrong connectDEX error: %v", err) } - rig.db.acct = nil - rig.db.acctErr = tErr + form.Addr = tDexHost // asset not found - dcrAsset := dc.assets[tDCR.ID] - delete(dc.assets, tDCR.ID) + cfgAssets := dc.cfg.Assets + mkts := dc.cfg.Markets + dc.cfg.Assets = dc.cfg.Assets[1:] + dc.cfg.Markets = []*msgjson.Market{} + rig.queueConfig() run() - if err == nil { - t.Fatalf("no error for missing asset") + if !errorHasCode(err, assetSupportErr) { + t.Fatalf("wrong error for missing asset: %v", err) + } + dc.cfg.Assets = cfgAssets + dc.cfg.Markets = mkts + + // error creating signing key + rig.crypter.encryptErr = tErr + rig.queueConfig() + run() + if !errorHasCode(err, acctKeyErr) { + t.Fatalf("wrong account key error: %v", err) } - dc.assets[tDCR.ID] = dcrAsset + rig.crypter.encryptErr = nil // register request error + rig.queueConfig() rig.ws.queueResponse(msgjson.RegisterRoute, func(msg *msgjson.Message, f msgFunc) error { return tErr }) run() - if err == nil { - t.Fatalf("no error for register request error") + if !errorHasCode(err, registerErr) { + t.Fatalf("wrong error for register request error: %v", err) } // signature error goodSig := regRes.Sig regRes.Sig = []byte("badsig") - queueRegister() + rig.queueConfig() + rig.queueRegister(regRes) run() - if err == nil { - t.Fatalf("no error for bad signature on register response") + if !errorHasCode(err, signatureErr) { + t.Fatalf("wrong error for bad signature on register response: %v", err) } regRes.Sig = goodSig // zero fee error goodFee := regRes.Fee regRes.Fee = 0 - queueRegister() + rig.queueConfig() + rig.queueRegister(regRes) run() - if err == nil { - t.Fatalf("no error for zero fee") + if !errorHasCode(err, zeroFeeErr) { + t.Fatalf("wrong error for zero fee: %v", err) + } + + // wrong fee error + regRes.Fee = tFee + 1 + rig.queueConfig() + rig.queueRegister(regRes) + run() + if !errorHasCode(err, feeMismatchErr) { + t.Fatalf("wrong error for wrong fee: %v", err) } regRes.Fee = goodFee + // Form fee error + form.Fee = tFee + 1 + rig.queueConfig() + rig.queueRegister(regRes) + run() + if !errorHasCode(err, feeMismatchErr) { + t.Fatalf("wrong error for wrong fee in form: %v", err) + } + form.Fee = tFee + // PayFee error - queueRegister() + rig.queueConfig() + rig.queueRegister(regRes) tWallet.payFeeErr = tErr run() - if err == nil { + if !errorHasCode(err, feeSendErr) { t.Fatalf("no error for PayFee error") } tWallet.payFeeErr = nil - // May want to smarten up error handling in the coin waiter loop. If so - // this check can be re-implemented. - // // coin confirmation error - // queueRegister() - // tWallet.payFeeCoin.confsErr = tErr - // run() - // if err == nil { - // t.Fatalf("no error for coin confirmation error") - // } - // tWallet.payFeeCoin.confsErr = nil - // notifyfee response error - queueRegister() + rig.queueConfig() + rig.queueRegister(regRes) queueTipChange() rig.ws.queueResponse(msgjson.NotifyFeeRoute, func(msg *msgjson.Message, f msgFunc) error { m, _ := msgjson.NewResponse(msg.ID, nil, msgjson.NewError(1, "test error message")) @@ -1278,16 +1367,7 @@ func TestLogin(t *testing.T) { tCore := rig.core rig.acct.markFeePaid() - queueSuccess := func() { - rig.ws.queueResponse(msgjson.ConnectRoute, func(msg *msgjson.Message, f msgFunc) error { - result := &msgjson.ConnectResult{} - resp, _ := msgjson.NewResponse(msg.ID, result, nil) - f(resp) - return nil - }) - } - - queueSuccess() + rig.queueConnect() _, err := tCore.Login(tPW) if err != nil || !rig.acct.authed() { t.Fatalf("initial Login error: %v", err) @@ -1328,7 +1408,7 @@ func TestLogin(t *testing.T) { } // Success again. - queueSuccess() + rig.queueConnect() _, err = tCore.Login(tPW) if err != nil || !rig.acct.authed() { t.Fatalf("final Login error: %v", err) @@ -1343,25 +1423,14 @@ func TestConnectDEX(t *testing.T) { Host: "somedex.com", } - queueConfig := func() { - rig.ws.queueResponse(msgjson.ConfigRoute, func(msg *msgjson.Message, f msgFunc) error { - result := &msgjson.ConfigResult{ - BroadcastTimeout: 5 * 60 * 1000, - } - resp, _ := msgjson.NewResponse(msg.ID, result, nil) - f(resp) - return nil - }) - } - - queueConfig() + rig.queueConfig() _, err := tCore.connectDEX(ai) if err != nil { t.Fatalf("initial connectDEX error: %v", err) } // Bad URL. - ai.Host = ":::" + ai.Host = tUnparseableHost // Illegal ASCIII control character _, err = tCore.connectDEX(ai) if err == nil { t.Fatalf("no error for bad URL") @@ -1399,7 +1468,7 @@ func TestConnectDEX(t *testing.T) { } // Success again. - queueConfig() + rig.queueConfig() _, err = tCore.connectDEX(ai) if err != nil { t.Fatalf("final connectDEX error: %v", err) diff --git a/client/core/errors.go b/client/core/errors.go new file mode 100644 index 0000000000..a247bcdab9 --- /dev/null +++ b/client/core/errors.go @@ -0,0 +1,58 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org/license/1.0.0. + +package core + +import ( + "errors" + "fmt" +) + +const ( + walletErr = iota + walletAuthErr + dupeDEXErr + assetSupportErr + registerErr + signatureErr + zeroFeeErr + feeMismatchErr + feeSendErr + passwordErr + emptyHostErr + connectionErr + acctKeyErr +) + +// Error is an error message and an error code. +type Error struct { + s string + code int +} + +// Error returns the error string. Satisfies the error interface. +func (e *Error) Error() string { + return e.s +} + +// newError is a constructor for a new Error. +func newError(code int, s string, a ...interface{}) error { + return &Error{ + s: fmt.Sprintf(s, a...), + code: code, + } +} + +// codedError converts the error to an Error with the specified code. +func codedError(code int, err error) error { + return &Error{ + s: err.Error(), + code: code, + } +} + +// errorHasCode checks whether the error is an Error and has the specified code. +func errorHasCode(err error, code int) bool { + var e *Error + return errors.As(err, &e) && e.code == code +}