From d52ef5c62720f8a01421e8276b3a0dafd8552682 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Tue, 3 Sep 2024 20:39:39 +0100 Subject: [PATCH 1/5] multi: update AccountDisable method on core and db - Rename AccountDisable to ToggleAccountStatus and allow re-enabling a disabled account. Signed-off-by: Philemon Ukane --- client/core/account.go | 47 ++++---- client/core/account_test.go | 57 +++++----- client/core/core.go | 6 ++ client/core/core_test.go | 8 +- client/core/errors.go | 2 +- client/db/bolt/db.go | 113 +++++++------------- client/db/bolt/db_test.go | 44 +++++--- client/db/interface.go | 5 +- client/db/types.go | 1 + client/webserver/api.go | 14 +-- client/webserver/live_test.go | 18 ++-- client/webserver/site/src/js/dexsettings.ts | 4 +- client/webserver/types.go | 7 +- client/webserver/webserver.go | 4 +- client/webserver/webserver_test.go | 2 +- 15 files changed, 169 insertions(+), 163 deletions(-) diff --git a/client/core/account.go b/client/core/account.go index 04b031c8d5..9e60f969a4 100644 --- a/client/core/account.go +++ b/client/core/account.go @@ -41,35 +41,44 @@ func (c *Core) disconnectDEX(dc *dexConnection) { c.connMtx.Unlock() } -// AccountDisable is used to disable an account by given host and application -// password. -func (c *Core) AccountDisable(pw []byte, addr string) error { +// ToggleAccountStatus is used to disable or enable an account by given host and +// application password. +func (c *Core) ToggleAccountStatus(pw []byte, addr string, disable bool) error { // Validate password. _, err := c.encryptionKey(pw) if err != nil { return codedError(passwordErr, err) } - // Get dex connection by host. - dc, _, err := c.dex(addr) - if err != nil { - return newError(unknownDEXErr, "error retrieving dex conn: %w", err) - } + var dc *dexConnection + if disable { + // Get dex connection by host. + dc, _, err = c.dex(addr) + if err != nil { + return newError(unknownDEXErr, "error retrieving dex conn: %w", err) + } - // Check active orders or bonds. - if dc.hasActiveOrders() { - return fmt.Errorf("cannot disable account with active orders") - } - if dc.hasUnspentBond() { - return fmt.Errorf("cannot disable account with unspent bonds") + // Check active orders or bonds. + if dc.hasActiveOrders() { + return fmt.Errorf("cannot disable account with active orders") + } } - err = c.db.DisableAccount(dc.acct.host) + err = c.db.ToggleAccountStatus(addr, disable) if err != nil { - return newError(accountDisableErr, "error disabling account: %w", err) + return newError(accountStatusUpdateErr, "error updating account status: %w", err) } - c.disconnectDEX(dc) + if disable { + c.disconnectDEX(dc) + } else { + acct, err := c.db.Account(addr) + if err != nil { + return err + } + + c.connectAccount(acct) + } return nil } @@ -368,9 +377,9 @@ func (c *Core) UpdateDEXHost(oldHost, newHost string, appPW []byte, certI any) ( } } - err = c.db.DisableAccount(oldDc.acct.host) + err = c.db.ToggleAccountStatus(oldDc.acct.host, true) if err != nil { - return nil, newError(accountDisableErr, "error disabling account: %w", err) + return nil, newError(accountStatusUpdateErr, "error updating account status: %w", err) } updatedHost = true diff --git a/client/core/account_test.go b/client/core/account_test.go index ddad6213ce..09a6aa1139 100644 --- a/client/core/account_test.go +++ b/client/core/account_test.go @@ -59,32 +59,36 @@ func TestAccountExport(t *testing.T) { } */ -func TestAccountDisable(t *testing.T) { +func TestToggleAccountStatus(t *testing.T) { activeTrades := map[order.OrderID]*trackedTrade{ {}: {metaData: &db.OrderMetaData{Status: order.OrderStatusBooked}}, } + // TODO: Add test case for enable dex account tests := []struct { - name, host string - recryptErr, acctErr, disableAcctErr error - wantErr, wantErrCode, loseConns bool - activeTrades map[order.OrderID]*trackedTrade - errCode int + name, host string + recryptErr, acctErr, disableAcctErr error + wantErr, wantErrCode, loseConns, wantDisable bool + activeTrades map[order.OrderID]*trackedTrade + errCode int }{{ - name: "ok", - host: tDexHost, + name: "ok", + host: tDexHost, + wantDisable: true, }, { - name: "password error", - host: tDexHost, - recryptErr: tErr, - wantErr: true, - errCode: passwordErr, + name: "password error", + host: tDexHost, + recryptErr: tErr, + wantErr: true, + errCode: passwordErr, + wantDisable: true, }, { name: "host error", host: ":bad:", wantErr: true, wantErrCode: true, errCode: unknownDEXErr, + wantDisable: true, }, { name: "dex not in conns", host: tDexHost, @@ -92,18 +96,21 @@ func TestAccountDisable(t *testing.T) { wantErr: true, wantErrCode: true, errCode: unknownDEXErr, + wantDisable: true, }, { name: "has active orders", host: tDexHost, activeTrades: activeTrades, wantErr: true, + wantDisable: true, }, { name: "disable account error", host: tDexHost, disableAcctErr: errors.New(""), wantErr: true, wantErrCode: true, - errCode: accountDisableErr, + errCode: accountStatusUpdateErr, + wantDisable: true, }} for _, test := range tests { @@ -122,7 +129,7 @@ func TestAccountDisable(t *testing.T) { } tCore.connMtx.Unlock() - err := tCore.AccountDisable(tPW, test.host) + err := tCore.ToggleAccountStatus(tPW, test.host, test.wantDisable) if test.wantErr { if err == nil { t.Fatalf("expected error for test %v", test.name) @@ -135,15 +142,17 @@ func TestAccountDisable(t *testing.T) { if err != nil { t.Fatalf("unexpected error for test %v: %v", test.name, err) } - if _, found := tCore.conns[test.host]; found { - t.Fatal("found disabled account dex connection") - } - if rig.db.disabledHost == nil { - t.Fatal("expected execution of db.DisableAccount") - } - if *rig.db.disabledHost != test.host { - t.Fatalf("expected db disabled account to match test host, want: %v"+ - " got: %v", test.host, *rig.db.disabledHost) + if test.wantDisable { + if _, found := tCore.conns[test.host]; found { + t.Fatal("found disabled account dex connection") + } + if rig.db.disabledHost == nil { + t.Fatal("expected a disable dex server host") + } + if *rig.db.disabledHost != test.host { + t.Fatalf("expected db account to match test host, want: %v"+ + " got: %v", test.host, *rig.db.disabledHost) + } } } } diff --git a/client/core/core.go b/client/core/core.go index b67d5c7ae3..2af05c5e02 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -7113,6 +7113,12 @@ func (c *Core) initialize() error { var liveConns uint32 var wg sync.WaitGroup for _, acct := range accts { + if !acct.Active { + // TODO: We should list this account separatly for display on the + // dex settings page to allow re-enabling this server. But we should + // listen for unspent bond refund if any. + continue + } wg.Add(1) go func(acct *db.AccountInfo) { defer wg.Done() diff --git a/client/core/core_test.go b/client/core/core_test.go index 8f6fa9c5b0..85a3aa07f9 100644 --- a/client/core/core_test.go +++ b/client/core/core_test.go @@ -420,8 +420,12 @@ func (tdb *TDB) BondRefunded(host string, assetID uint32, bondCoinID []byte) err return nil } -func (tdb *TDB) DisableAccount(url string) error { - tdb.disabledHost = &url +func (tdb *TDB) ToggleAccountStatus(host string, disable bool) error { + if disable { + tdb.disabledHost = &host + } else { + tdb.disabledHost = nil + } return tdb.disableAccountErr } diff --git a/client/core/errors.go b/client/core/errors.go index f5de3a1f8a..1c30e850dc 100644 --- a/client/core/errors.go +++ b/client/core/errors.go @@ -42,7 +42,7 @@ const ( fileReadErr unknownDEXErr accountRetrieveErr - accountDisableErr + accountStatusUpdateErr suspendedAcctErr existenceCheckErr createWalletErr diff --git a/client/db/bolt/db.go b/client/db/bolt/db.go index 09130b986c..a1dd34986b 100644 --- a/client/db/bolt/db.go +++ b/client/db/bolt/db.go @@ -55,20 +55,19 @@ var ( // value encodings. var ( // bucket keys - appBucket = []byte("appBucket") - accountsBucket = []byte("accounts") - bondIndexesBucket = []byte("bondIndexes") - bondsSubBucket = []byte("bonds") // sub bucket of accounts - disabledAccountsBucket = []byte("disabledAccounts") - activeOrdersBucket = []byte("activeOrders") - archivedOrdersBucket = []byte("orders") - activeMatchesBucket = []byte("activeMatches") - archivedMatchesBucket = []byte("matches") - botProgramsBucket = []byte("botPrograms") - walletsBucket = []byte("wallets") - notesBucket = []byte("notes") - pokesBucket = []byte("pokes") - credentialsBucket = []byte("credentials") + appBucket = []byte("appBucket") + accountsBucket = []byte("accounts") + bondIndexesBucket = []byte("bondIndexes") + bondsSubBucket = []byte("bonds") // sub bucket of accounts + activeOrdersBucket = []byte("activeOrders") + archivedOrdersBucket = []byte("orders") + activeMatchesBucket = []byte("activeMatches") + archivedMatchesBucket = []byte("matches") + botProgramsBucket = []byte("botPrograms") + walletsBucket = []byte("wallets") + notesBucket = []byte("notes") + pokesBucket = []byte("pokes") + credentialsBucket = []byte("credentials") // value keys versionKey = []byte("version") @@ -179,7 +178,7 @@ func NewDB(dbPath string, logger dex.Logger, opts ...Opts) (dexdb.DB, error) { } if err = bdb.makeTopLevelBuckets([][]byte{ - appBucket, accountsBucket, bondIndexesBucket, disabledAccountsBucket, + appBucket, accountsBucket, bondIndexesBucket, activeOrdersBucket, archivedOrdersBucket, activeMatchesBucket, archivedMatchesBucket, walletsBucket, notesBucket, credentialsBucket, @@ -548,6 +547,8 @@ func loadAccountInfo(acct *bbolt.Bucket, log dex.Logger) (*db.AccountInfo, error return nil, err } + acctInfo.Active = bytes.Equal(acct.Get(activeKey), byteTrue) + bondsBkt := acct.Bucket(bondsSubBucket) if bondsBkt == nil { return acctInfo, nil // no bonds, OK for legacy account @@ -627,7 +628,7 @@ func (db *BoltDB) CreateAccount(ai *dexdb.AccountInfo) error { if err != nil { return fmt.Errorf("accountKey put error: %w", err) } - err = acct.Put(activeKey, byteTrue) // huh? + err = acct.Put(activeKey, byteTrue) if err != nil { return fmt.Errorf("activeKey put error: %w", err) } @@ -710,63 +711,31 @@ func (db *BoltDB) UpdateAccountInfo(ai *dexdb.AccountInfo) error { }) } -// deleteAccount removes the account by host. -func (db *BoltDB) deleteAccount(host string) error { - acctKey := []byte(host) +// ToggleAccountStatus enables or disables the account associated with the given +// host. +func (db *BoltDB) ToggleAccountStatus(host string, disable bool) error { return db.acctsUpdate(func(accts *bbolt.Bucket) error { - return accts.DeleteBucket(acctKey) - }) -} - -// DisableAccount disables the account associated with the given host -// and archives it. The Accounts and Account methods will no longer find -// the disabled account. -// -// TODO: Add disabledAccounts method for retrieval of a disabled account and -// possible recovery of the account data. -func (db *BoltDB) DisableAccount(url string) error { - // Get account's info. - ai, err := db.Account(url) - if err != nil { - return err - } - // Copy AccountInfo to disabledAccounts. Not necessary for view-only - // accounts. - acctKey := ai.EncKey() - if len(acctKey) > 0 { - err = db.disabledAcctsUpdate(func(disabledAccounts *bbolt.Bucket) error { - return disabledAccounts.Put(acctKey, ai.Encode()) - }) - if err != nil { - return err + acct := accts.Bucket([]byte(host)) + if acct == nil { + return fmt.Errorf("account not found for %s", host) } - } - // WARNING/TODO: account proof (fee paid info) not saved! - err = db.deleteAccount(ai.Host) - if err != nil { - if errors.Is(err, bbolt.ErrBucketNotFound) { - db.log.Warnf("Cannot delete account from active accounts"+ - " table. Host: not found. %s err: %v", ai.Host, err) - } else { - return err + + newStatus := byteTrue + if disable { + newStatus = byteFalse } - } - return nil -} -// disabledAccount gets the AccountInfo from disabledAccount associated with -// the specified EncKey. -func (db *BoltDB) disabledAccount(encKey []byte) (*dexdb.AccountInfo, error) { - var acctInfo *dexdb.AccountInfo - return acctInfo, db.disabledAcctsView(func(accts *bbolt.Bucket) error { - acct := accts.Get(encKey) - if acct == nil { - return fmt.Errorf("account not found for key") + if bytes.Equal(acct.Get(activeKey), newStatus) { + msg := "account is already enabled" + if disable { + msg = "account is already disabled" + } + return errors.New(msg) } - var err error - acctInfo, err = dexdb.DecodeAccountInfo(acct) + + err := acct.Put(activeKey, newStatus) if err != nil { - return err + return fmt.Errorf("accountKey put error: %w", err) } return nil }) @@ -782,16 +751,6 @@ func (db *BoltDB) acctsUpdate(f bucketFunc) error { return db.withBucket(accountsBucket, db.Update, f) } -// disabledAcctsView is a convenience function for reading from the disabledAccounts bucket. -func (db *BoltDB) disabledAcctsView(f bucketFunc) error { - return db.withBucket(disabledAccountsBucket, db.View, f) -} - -// disabledAcctsUpdate is a convenience function for inserting into the disabledAccounts bucket. -func (db *BoltDB) disabledAcctsUpdate(f bucketFunc) error { - return db.withBucket(disabledAccountsBucket, db.Update, f) -} - func (db *BoltDB) storeBond(bondBkt *bbolt.Bucket, bond *db.Bond) error { err := bondBkt.Put(bondKey, bond.Encode()) if err != nil { diff --git a/client/db/bolt/db_test.go b/client/db/bolt/db_test.go index f34d096fd2..35c48039b1 100644 --- a/client/db/bolt/db_test.go +++ b/client/db/bolt/db_test.go @@ -266,7 +266,7 @@ func TestAccounts(t *testing.T) { acct.DEXPubKey = dexKey } -func TestDisableAccount(t *testing.T) { +func TestToggleAccountStatus(t *testing.T) { boltdb, shutdown := newTestDB(t) defer shutdown() @@ -276,29 +276,43 @@ func TestDisableAccount(t *testing.T) { if err != nil { t.Fatalf("Unexpected CreateAccount error: %v", err) } - actualDisabledAccount, err := boltdb.disabledAccount(acct.EncKey()) - if err == nil { - t.Fatalf("Expected disabledAccount error but there was none.") + + accounts, err := boltdb.Accounts() + if err != nil { + t.Fatalf("Unexpected boltdb.Accounts error: %v", err) } - if actualDisabledAccount != nil { - t.Fatalf("Expected not to retrieve a disabledAccount.") + if len(accounts) != 1 { + t.Fatalf("Expected 1 account but got %d", len(accounts)) } - err = boltdb.DisableAccount(host) + // Test disable account + err = boltdb.ToggleAccountStatus(host, true) + if err != nil { + t.Fatalf("Unexpected ToggleAccountStatus error: %v", err) + } + actualAcct, err := boltdb.Account(host) if err != nil { - t.Fatalf("Unexpected DisableAccount error: %v", err) + t.Fatalf("Unexpected boltdb.Account error: %v", err) } - actualAcct, _ := boltdb.Account(host) - if actualAcct != nil { - t.Fatalf("Expected retrieval of deleted account to be nil") + + if actualAcct.Active { + t.Fatalf("Expected a disabled account.") } - actualDisabledAccount, err = boltdb.disabledAccount(acct.EncKey()) + + // Test enable account + err = boltdb.ToggleAccountStatus(host, false) if err != nil { - t.Fatalf("Unexpected disabledAccount error: %v", err) + t.Fatalf("Unexpected ToggleAccountStatus error: %v", err) } - if actualDisabledAccount == nil { - t.Fatalf("Expected to retrieve a disabledAccount.") + + actualAcct, err = boltdb.Account(host) + if err != nil { + t.Fatalf("Unexpected boltdb.Account error: %v", err) + } + + if !actualAcct.Active { + t.Fatalf("Expected an active account.") } } diff --git a/client/db/interface.go b/client/db/interface.go index beba5b8ec9..215de2dcec 100644 --- a/client/db/interface.go +++ b/client/db/interface.go @@ -47,8 +47,9 @@ type DB interface { ConfirmBond(host string, assetID uint32, bondCoinID []byte) error // BondRefunded records that a bond has been refunded. BondRefunded(host string, assetID uint32, bondCoinID []byte) error - // DisableAccount sets the AccountInfo disabled status to true. - DisableAccount(host string) error + // ToggleAccountStatus enables or disables the account associated with the + // given host. + ToggleAccountStatus(host string, disable bool) error // UpdateOrder saves the order information in the database. Any existing // order info will be overwritten without indication. UpdateOrder(m *MetaOrder) error diff --git a/client/db/types.go b/client/db/types.go index 05494c1121..e23c1b6cc0 100644 --- a/client/db/types.go +++ b/client/db/types.go @@ -232,6 +232,7 @@ type AccountInfo struct { MaxBondedAmt uint64 PenaltyComps uint16 BondAsset uint32 // the asset to use when auto-posting bonds + Active bool // whether the account is enabled // DEPRECATED reg fee data. Bond txns are in a sub-bucket. // Left until we need to upgrade just for serialization simplicity. diff --git a/client/webserver/api.go b/client/webserver/api.go index e6c3f5d0be..88b56a0303 100644 --- a/client/webserver/api.go +++ b/client/webserver/api.go @@ -838,9 +838,9 @@ func (s *WebServer) apiRestoreWalletInfo(w http.ResponseWriter, r *http.Request) writeJSON(w, resp) } -// apiAccountDisable is the handler for the '/disableaccount' API request. -func (s *WebServer) apiAccountDisable(w http.ResponseWriter, r *http.Request) { - form := new(accountDisableForm) +// apiToggleAccountStatus is the handler for the '/toggleaccountstatus' API request. +func (s *WebServer) apiToggleAccountStatus(w http.ResponseWriter, r *http.Request) { + form := new(updateAccountStatusForm) defer form.Pass.Clear() if !readPost(w, r, form) { return @@ -852,12 +852,14 @@ func (s *WebServer) apiAccountDisable(w http.ResponseWriter, r *http.Request) { return } // Disable account. - err = s.core.AccountDisable(appPW, form.Host) + err = s.core.ToggleAccountStatus(appPW, form.Host, form.Disable) if err != nil { - s.writeAPIError(w, fmt.Errorf("error disabling account: %w", err)) + s.writeAPIError(w, fmt.Errorf("error updating account status: %w", err)) return } - w.Header().Set("Connection", "close") + if form.Disable { + w.Header().Set("Connection", "close") + } writeJSON(w, simpleAck()) } diff --git a/client/webserver/live_test.go b/client/webserver/live_test.go index 66f012998f..cae2faf0c1 100644 --- a/client/webserver/live_test.go +++ b/client/webserver/live_test.go @@ -772,7 +772,7 @@ func (c *TCore) AccountExport(pw []byte, host string) (*core.Account, []*db.Bond func (c *TCore) AccountImport(pw []byte, account *core.Account, bond []*db.Bond) error { return nil } -func (c *TCore) AccountDisable(pw []byte, host string) error { return nil } +func (c *TCore) ToggleAccountStatus(pw []byte, host string, disable bool) error { return nil } func (c *TCore) TxHistory(assetID uint32, n int, refID *string, past bool) ([]*asset.WalletTransaction, error) { return nil, nil @@ -2176,13 +2176,13 @@ func (m *TMarketMaker) StartBot(startCfg *mm.StartConfig, alternateConfigPath *s mkt.BaseID: randomBalance(), }, DEXBalances: map[uint32]*mm.BotBalance{ - mkt.BaseID: &mm.BotBalance{ + mkt.BaseID: { Available: randomBalance(), Locked: randomBalance(), Pending: randomBalance(), Reserved: randomBalance(), }, - mkt.BaseID: &mm.BotBalance{ + mkt.BaseID: { Available: randomBalance(), Locked: randomBalance(), Pending: randomBalance(), @@ -2190,13 +2190,13 @@ func (m *TMarketMaker) StartBot(startCfg *mm.StartConfig, alternateConfigPath *s }, }, CEXBalances: map[uint32]*mm.BotBalance{ - mkt.BaseID: &mm.BotBalance{ + mkt.BaseID: { Available: randomBalance(), Locked: randomBalance(), Pending: randomBalance(), Reserved: randomBalance(), }, - mkt.BaseID: &mm.BotBalance{ + mkt.BaseID: { Available: randomBalance(), Locked: randomBalance(), Pending: randomBalance(), @@ -2313,12 +2313,12 @@ func (m *TMarketMaker) Status() *mm.Status { stats = &mm.RunStats{ InitialBalances: make(map[uint32]uint64), DEXBalances: map[uint32]*mm.BotBalance{ - botCfg.BaseID: &mm.BotBalance{Available: randomBalance()}, - botCfg.QuoteID: &mm.BotBalance{Available: randomBalance()}, + botCfg.BaseID: {Available: randomBalance()}, + botCfg.QuoteID: {Available: randomBalance()}, }, CEXBalances: map[uint32]*mm.BotBalance{ - botCfg.BaseID: &mm.BotBalance{Available: randomBalance()}, - botCfg.QuoteID: &mm.BotBalance{Available: randomBalance()}, + botCfg.BaseID: {Available: randomBalance()}, + botCfg.QuoteID: {Available: randomBalance()}, }, ProfitLoss: randomProfitLoss(botCfg.BaseID, botCfg.QuoteID), StartTime: time.Now().Add(-time.Duration(float64(time.Hour*10) * rand.Float64())).Unix(), diff --git a/client/webserver/site/src/js/dexsettings.ts b/client/webserver/site/src/js/dexsettings.ts index 0fa3d0efa4..a23839a7cc 100644 --- a/client/webserver/site/src/js/dexsettings.ts +++ b/client/webserver/site/src/js/dexsettings.ts @@ -325,9 +325,9 @@ export default class DexSettingsPage extends BasePage { async disableAccount () { const page = this.page const host = page.disableAccountHost.textContent - const req = { host } + const req = { host, disable: true } const loaded = app().loading(this.body) - const res = await postJSON('/api/disableaccount', req) + const res = await postJSON('/api/toggleaccountstatus', req) loaded() if (!app().checkResponse(res)) { page.disableAccountErr.textContent = res.msg diff --git a/client/webserver/types.go b/client/webserver/types.go index 6ec08206ab..f5b8e48be4 100644 --- a/client/webserver/types.go +++ b/client/webserver/types.go @@ -133,9 +133,10 @@ type accountImportForm struct { Bonds []*db.Bond `json:"bonds"` } -type accountDisableForm struct { - Pass encode.PassBytes `json:"pw"` - Host string `json:"host"` +type updateAccountStatusForm struct { + Pass encode.PassBytes `json:"pw"` + Host string `json:"host"` + Disable bool `json:"disable"` } type deleteRecordsForm struct { diff --git a/client/webserver/webserver.go b/client/webserver/webserver.go index 7a44b1da68..1ea09fa6fe 100644 --- a/client/webserver/webserver.go +++ b/client/webserver/webserver.go @@ -133,7 +133,7 @@ type clientCore interface { MaxSell(host string, base, quote uint32) (*core.MaxOrderEstimate, error) AccountExport(pw []byte, host string) (*core.Account, []*db.Bond, error) AccountImport(pw []byte, account *core.Account, bonds []*db.Bond) error - AccountDisable(pw []byte, host string) error + ToggleAccountStatus(pw []byte, host string, disable bool) error IsInitialized() bool ExportSeed(pw []byte) (string, error) PreOrder(*core.TradeForm) (*core.OrderEstimate, error) @@ -536,7 +536,7 @@ func New(cfg *Config) (*WebServer, error) { apiAuth.Post("/exportaccount", s.apiAccountExport) apiAuth.Post("/exportseed", s.apiExportSeed) apiAuth.Post("/importaccount", s.apiAccountImport) - apiAuth.Post("/disableaccount", s.apiAccountDisable) + apiAuth.Post("/toggleaccountstatus", s.apiToggleAccountStatus) apiAuth.Post("/accelerateorder", s.apiAccelerateOrder) apiAuth.Post("/preaccelerate", s.apiPreAccelerate) apiAuth.Post("/accelerationestimate", s.apiAccelerationEstimate) diff --git a/client/webserver/webserver_test.go b/client/webserver/webserver_test.go index 2ada5aa220..173e7628e0 100644 --- a/client/webserver/webserver_test.go +++ b/client/webserver/webserver_test.go @@ -246,7 +246,7 @@ func (c *TCore) AccountExport(pw []byte, host string) (*core.Account, []*db.Bond func (c *TCore) AccountImport(pw []byte, account *core.Account, bonds []*db.Bond) error { return nil } -func (c *TCore) AccountDisable(pw []byte, host string) error { return nil } +func (c *TCore) ToggleAccountStatus(pw []byte, host string, disable bool) error { return nil } func (c *TCore) ExportSeed(pw []byte) (string, error) { return "seed words here", nil From 8c23904c84b49d897f3defe8a5d0d10671f8b8c0 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Wed, 4 Sep 2024 09:24:53 +0100 Subject: [PATCH 2/5] site: don't convert null/undefined values to objects Signed-off-by: Philemon Ukane --- client/webserver/site/src/js/forms.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client/webserver/site/src/js/forms.ts b/client/webserver/site/src/js/forms.ts index 40ffb097a8..b7e801eb3b 100644 --- a/client/webserver/site/src/js/forms.ts +++ b/client/webserver/site/src/js/forms.ts @@ -1037,13 +1037,17 @@ export class FeeAssetSelectionForm { this.marketRows.push({ mkt, tmpl, setTier }) } - for (const { symbol, id: assetID } of Object.values(xc.assets)) { - if (!app().assets[assetID]) continue - const bondAsset = xc.bondAssets[symbol] - if (bondAsset) addBondRow(assetID, bondAsset) + if (xc.assets) { + for (const { symbol, id: assetID } of Object.values(xc.assets)) { + if (!app().assets[assetID]) continue + const bondAsset = xc.bondAssets[symbol] + if (bondAsset) addBondRow(assetID, bondAsset) + } } - for (const mkt of Object.values(xc.markets)) addMarketRow(mkt) + if (xc.markets) { + for (const mkt of Object.values(xc.markets)) addMarketRow(mkt) + } // page.host.textContent = xc.host page.tradingTierInput.value = xc.auth.targetTier ? String(xc.auth.targetTier) : '1' From 11883cb67979dd1284349f8332420ce6d70d37d9 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Wed, 4 Sep 2024 09:44:41 +0100 Subject: [PATCH 3/5] multi: allow enabling and disabling of dex account Signed-off-by: Philemon Ukane --- client/core/account.go | 38 +++++++++------ client/core/bond.go | 8 +++- client/core/core.go | 23 ++++++---- client/core/types.go | 15 ++++++ client/db/bolt/db.go | 2 +- client/db/bolt/db_test.go | 4 +- client/db/types.go | 2 +- client/webserver/jsintl.go | 8 ++++ client/webserver/locales/en-us.go | 3 +- .../webserver/site/src/html/dexsettings.tmpl | 32 ++++++++----- client/webserver/site/src/js/dexsettings.ts | 46 ++++++++++++++----- client/webserver/site/src/js/locales.ts | 4 ++ client/webserver/site/src/js/markets.ts | 4 +- client/webserver/site/src/js/registry.ts | 1 + 14 files changed, 136 insertions(+), 54 deletions(-) diff --git a/client/core/account.go b/client/core/account.go index 9e60f969a4..c604c95c9b 100644 --- a/client/core/account.go +++ b/client/core/account.go @@ -14,9 +14,9 @@ import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" ) -// disconnectDEX unsubscribes from the dex's orderbooks, ends the connection -// with the dex, and removes it from the connection map. -func (c *Core) disconnectDEX(dc *dexConnection) { +// stopDEXConnection unsubscribes from the dex's orderbooks and ends the +// connection with the dex. The dexConnection will still remain in c.conns map. +func (c *Core) stopDEXConnection(dc *dexConnection) { // Stop dexConnection books. dc.cfgMtx.RLock() if dc.cfg != nil { @@ -34,8 +34,13 @@ func (c *Core) disconnectDEX(dc *dexConnection) { } } dc.cfgMtx.RUnlock() + dc.connMaster.Disconnect() // disconnect +} + +// disconnectDEX disconnects a dex and removes it from the connection map. +func (c *Core) disconnectDEX(dc *dexConnection) { // Disconnect and delete connection from map. - dc.connMaster.Disconnect() + c.stopDEXConnection(dc) c.connMtx.Lock() delete(c.conns, dc.acct.host) c.connMtx.Unlock() @@ -45,19 +50,19 @@ func (c *Core) disconnectDEX(dc *dexConnection) { // application password. func (c *Core) ToggleAccountStatus(pw []byte, addr string, disable bool) error { // Validate password. - _, err := c.encryptionKey(pw) + crypter, err := c.encryptionKey(pw) if err != nil { return codedError(passwordErr, err) } - var dc *dexConnection - if disable { - // Get dex connection by host. - dc, _, err = c.dex(addr) - if err != nil { - return newError(unknownDEXErr, "error retrieving dex conn: %w", err) - } + // Get dex connection by host. All exchange servers (enabled or not) are loaded as + // dexConnections but disabled servers are not connected. + dc, _, err := c.dex(addr) + if err != nil { + return newError(unknownDEXErr, "error retrieving dex conn: %w", err) + } + if disable { // Check active orders or bonds. if dc.hasActiveOrders() { return fmt.Errorf("cannot disable account with active orders") @@ -70,14 +75,19 @@ func (c *Core) ToggleAccountStatus(pw []byte, addr string, disable bool) error { } if disable { - c.disconnectDEX(dc) + dc.acct.toggleAccountStatus(true) + c.stopDEXConnection(dc) } else { acct, err := c.db.Account(addr) if err != nil { return err } - c.connectAccount(acct) + if !c.connectAccount(acct) { + c.log.Errorf("Failed to establish connection to %s (will retry)", addr) + } + + c.initializeDEXConnections(crypter) } return nil diff --git a/client/core/bond.go b/client/core/bond.go index 291ee5b838..b544d2c5ec 100644 --- a/client/core/bond.go +++ b/client/core/bond.go @@ -713,8 +713,6 @@ func (c *Core) rotateBonds(ctx context.Context) { } acctBondState := c.bondStateOfDEX(dc, bondCfg) - c.repostPendingBonds(dc, bondCfg, acctBondState, unlocked) - refundedAssets, expiredStrength, err := c.refundExpiredBonds(ctx, dc.acct, bondCfg, acctBondState, now) if err != nil { c.log.Errorf("Failed to refund expired bonds for %v: %v", dc.acct.host, err) @@ -724,6 +722,12 @@ func (c *Core) rotateBonds(ctx context.Context) { c.updateAssetBalance(assetID) } + if dc.acct.isDisabled() { + continue // we can only attempt bond refund(if any) for disabled accounts + } + + c.repostPendingBonds(dc, bondCfg, acctBondState, unlocked) + bondAsset := bondCfg.bondAssets[acctBondState.BondAssetID] if bondAsset == nil { if acctBondState.TargetTier > 0 { diff --git a/client/core/core.go b/client/core/core.go index 2af05c5e02..d5635c8a8f 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -455,6 +455,7 @@ func (c *Core) exchangeInfo(dc *dexConnection) *Exchange { Host: dc.acct.host, AcctID: acctID, ConnectionStatus: dc.status(), + Disabled: dc.acct.isDisabled(), } } @@ -493,6 +494,7 @@ func (c *Core) exchangeInfo(dc *dexConnection) *Exchange { Auth: acctBondState.ExchangeAuth, MaxScore: cfg.MaxScore, PenaltyThreshold: cfg.PenaltyThreshold, + Disabled: dc.acct.isDisabled(), } } @@ -5138,6 +5140,10 @@ func (c *Core) initializeDEXConnections(crypter encrypt.Crypter) { continue } + if dc.acct.isDisabled() { + continue // we can only unlock the dex account to init the account ID. + } + // Unlock the bond wallet if a target tier is set. if bondAssetID, targetTier, maxBondedAmt := dc.bondOpts(); targetTier > 0 { c.log.Debugf("Preparing %s wallet to maintain target tier of %d for %v, bonding limit %v", @@ -7113,12 +7119,6 @@ func (c *Core) initialize() error { var liveConns uint32 var wg sync.WaitGroup for _, acct := range accts { - if !acct.Active { - // TODO: We should list this account separatly for display on the - // dex settings page to allow re-enabling this server. But we should - // listen for unspent bond refund if any. - continue - } wg.Add(1) go func(acct *db.AccountInfo) { defer wg.Done() @@ -7172,7 +7172,8 @@ func (c *Core) initialize() error { // connectAccount makes a connection to the DEX for the given account. If a // non-nil dexConnection is returned from newDEXConnection, it was inserted into // the conns map even if the connection attempt failed (connected == false), and -// the connect retry / keepalive loop is active. +// the connect retry / keepalive loop is active. The intial connection attempt +// or keepalive loop will not run if acct is disabled. func (c *Core) connectAccount(acct *db.AccountInfo) (connected bool) { host, err := addrHost(acct.Host) if err != nil { @@ -8156,7 +8157,7 @@ func (c *Core) startDexConnection(acctInfo *db.AccountInfo, dc *dexConnection) e // the dexConnection's ConnectionMaster is shut down. This goroutine should // be started as long as the reconnect loop is running. It only returns when // the wsConn is stopped. - listen := dc.broadcastingConnect() + listen := dc.broadcastingConnect() && !dc.acct.isDisabled() if listen { c.wg.Add(1) go c.listen(dc) @@ -8203,6 +8204,12 @@ func (c *Core) startDexConnection(acctInfo *db.AccountInfo, dc *dexConnection) e // according to ConnectResult.Bonds slice. } + if dc.acct.isDisabled() { + // Sort out the bonds with current time to indicate refundable bonds. + categorizeBonds(time.Now().Unix()) + return nil // nothing else to do + } + err := dc.connMaster.Connect(c.ctx) if err != nil { // Sort out the bonds with current time to indicate refundable bonds. diff --git a/client/core/types.go b/client/core/types.go index 39e830836f..f05a9f806e 100644 --- a/client/core/types.go +++ b/client/core/types.go @@ -714,6 +714,7 @@ type Exchange struct { Auth ExchangeAuth `json:"auth"` PenaltyThreshold uint32 `json:"penaltyThreshold"` MaxScore uint32 `json:"maxScore"` + Disabled bool `json:"disabled"` } // newDisplayIDFromSymbols creates a display-friendly market ID for a base/quote @@ -817,6 +818,7 @@ type dexAccount struct { authMtx sync.RWMutex isAuthed bool + disabled bool pendingBondsConfs map[string]uint32 pendingBonds []*db.Bond // not yet confirmed bonds []*db.Bond // confirmed, and not yet expired @@ -835,6 +837,7 @@ func newDEXAccount(acctInfo *db.AccountInfo, viewOnly bool) *dexAccount { cert: acctInfo.Cert, dexPubKey: acctInfo.DEXPubKey, viewOnly: viewOnly, + disabled: acctInfo.Disabled, encKey: acctInfo.EncKey(), // privKey and id on decrypt pendingBondsConfs: make(map[string]uint32), // bonds are set separately when categorized in connectDEX @@ -958,6 +961,18 @@ func (a *dexAccount) status() (initialized, unlocked bool) { return len(a.encKey) > 0, a.privKey != nil } +func (a *dexAccount) isDisabled() bool { + a.authMtx.RLock() + defer a.authMtx.RUnlock() + return a.disabled +} + +func (a *dexAccount) toggleAccountStatus(disable bool) { + a.authMtx.Lock() + defer a.authMtx.Unlock() + a.disabled = disable +} + // locked will be true if the account private key is currently decrypted, or // there are no account keys generated yet. func (a *dexAccount) locked() bool { diff --git a/client/db/bolt/db.go b/client/db/bolt/db.go index a1dd34986b..5e0b13c6b9 100644 --- a/client/db/bolt/db.go +++ b/client/db/bolt/db.go @@ -547,7 +547,7 @@ func loadAccountInfo(acct *bbolt.Bucket, log dex.Logger) (*db.AccountInfo, error return nil, err } - acctInfo.Active = bytes.Equal(acct.Get(activeKey), byteTrue) + acctInfo.Disabled = bytes.Equal(acct.Get(activeKey), byteFalse) bondsBkt := acct.Bucket(bondsSubBucket) if bondsBkt == nil { diff --git a/client/db/bolt/db_test.go b/client/db/bolt/db_test.go index 35c48039b1..341ee69d17 100644 --- a/client/db/bolt/db_test.go +++ b/client/db/bolt/db_test.go @@ -296,7 +296,7 @@ func TestToggleAccountStatus(t *testing.T) { t.Fatalf("Unexpected boltdb.Account error: %v", err) } - if actualAcct.Active { + if !actualAcct.Disabled { t.Fatalf("Expected a disabled account.") } @@ -311,7 +311,7 @@ func TestToggleAccountStatus(t *testing.T) { t.Fatalf("Unexpected boltdb.Account error: %v", err) } - if !actualAcct.Active { + if actualAcct.Disabled { t.Fatalf("Expected an active account.") } } diff --git a/client/db/types.go b/client/db/types.go index e23c1b6cc0..d20bd4fae5 100644 --- a/client/db/types.go +++ b/client/db/types.go @@ -232,7 +232,7 @@ type AccountInfo struct { MaxBondedAmt uint64 PenaltyComps uint16 BondAsset uint32 // the asset to use when auto-posting bonds - Active bool // whether the account is enabled + Disabled bool // whether the account is disabled // DEPRECATED reg fee data. Bond txns are in a sub-bucket. // Left until we need to upgrade just for serialization simplicity. diff --git a/client/webserver/jsintl.go b/client/webserver/jsintl.go index 2f7fa5d5a9..4cb29c25ef 100644 --- a/client/webserver/jsintl.go +++ b/client/webserver/jsintl.go @@ -195,6 +195,10 @@ const ( archivedSettingsID = "ARCHIVED_SETTINGS" idTransparent = "TRANSPARENT" idNoCodeProvided = "NO_CODE_PROVIDED" + enableAccount = "ENABLE_ACCOUNT" + disableAccount = "DISABLE_ACCOUNT" + accountDisabledMsg = "ACCOUNT_DISABLED_MSG" + dexDisabledMsg = "DEX_DISABLED_MSG" ) var enUS = map[string]*intl.Translation{ @@ -389,6 +393,10 @@ var enUS = map[string]*intl.Translation{ archivedSettingsID: {T: "Archived Settings"}, idTransparent: {T: "Transparent"}, idNoCodeProvided: {T: "no code provided"}, + enableAccount: {T: "Enable Account"}, + disableAccount: {T: "Disable Account"}, + accountDisabledMsg: {T: "account disabled - re-enable to update settings"}, + dexDisabledMsg: {T: "DEX server is disabled. Visit the settings page to enable and connect to this server."}, } var ptBR = map[string]*intl.Translation{ diff --git a/client/webserver/locales/en-us.go b/client/webserver/locales/en-us.go index cff6a9089e..841795abb2 100644 --- a/client/webserver/locales/en-us.go +++ b/client/webserver/locales/en-us.go @@ -51,7 +51,7 @@ var EnUS = map[string]*intl.Translation{ "Authorize Export": {T: "Authorize Export"}, "export_app_pw_msg": {T: "Enter your app password to confirm account export for"}, "Disable Account": {T: "Disable Account"}, - "disable_dex_server": {T: "This DEX server may be re-enabled at any time in the future by adding it again."}, + "disable_dex_server": {T: "This DEX server may be re-enabled at any time in the future on the settings page.", Version: 1}, "Authorize Import": {T: "Authorize Import"}, "app_pw_import_msg": {T: "Enter your app password to confirm account import"}, "Account File": {T: "Account File"}, @@ -653,4 +653,5 @@ var EnUS = map[string]*intl.Translation{ "Transaction": {T: "Transaction"}, "Value": {T: "Value"}, "Prepaid bond redeemed": {T: "Prepaid bond redeemed!"}, + "Enable Account": {T: "Enable Account"}, } diff --git a/client/webserver/site/src/html/dexsettings.tmpl b/client/webserver/site/src/html/dexsettings.tmpl index 55fd19c50b..36eb61640f 100644 --- a/client/webserver/site/src/html/dexsettings.tmpl +++ b/client/webserver/site/src/html/dexsettings.tmpl @@ -1,6 +1,6 @@ {{define "dexsettings"}} {{template "top" .}} -
+
@@ -21,11 +21,13 @@
- [[[target_tier]]] + [[[target_tier]]]
- [[[Actual Tier]]] + [[[Actual Tier]]]
@@ -39,7 +41,7 @@
- +
@@ -47,7 +49,7 @@
Auto Renew
-
+
@@ -61,7 +63,7 @@
- +
@@ -73,18 +75,24 @@
+
+ +
-
- -
-
+
[[[successful_cert_update]]]
-
+
@@ -144,4 +152,4 @@
{{template "bottom"}} -{{end}} +{{end}} \ No newline at end of file diff --git a/client/webserver/site/src/js/dexsettings.ts b/client/webserver/site/src/js/dexsettings.ts index a23839a7cc..5936441d8f 100644 --- a/client/webserver/site/src/js/dexsettings.ts +++ b/client/webserver/site/src/js/dexsettings.ts @@ -32,6 +32,7 @@ export default class DexSettingsPage extends BasePage { currentForm: PageElement page: Record host: string + accountDisabled:boolean keyup: (e: KeyboardEvent) => void dexAddrForm: forms.DEXAddressForm bondFeeBufferCache: Record @@ -101,7 +102,12 @@ export default class DexSettingsPage extends BasePage { this.reputationMeter.setHost(host) Doc.bind(page.exportDexBtn, 'click', () => this.exportAccount()) - Doc.bind(page.disableAcctBtn, 'click', () => this.prepareAccountDisable(page.disableAccountForm)) + + this.accountDisabled = body.dataset.disabled === 'true' + Doc.bind(page.toggleAccountStatusBtn, 'click', () => { + if (!this.accountDisabled) this.prepareAccountDisable(page.disableAccountForm) + else this.toggleAccountStatus(false) + }) Doc.bind(page.updateCertBtn, 'click', () => page.certFileInput.click()) Doc.bind(page.updateHostBtn, 'click', () => this.prepareUpdateHost()) Doc.bind(page.certFileInput, 'change', () => this.onCertFileChange()) @@ -114,12 +120,13 @@ export default class DexSettingsPage extends BasePage { Doc.bind(page.changeTier, 'click', () => { showTierForm() }) const willAutoRenew = xc.auth.targetTier > 0 this.renewToggle = new AniToggle(page.toggleAutoRenew, page.renewErr, willAutoRenew, async (newState: boolean) => { + if (this.accountDisabled) return if (newState) showTierForm() else return this.disableAutoRenew() }) Doc.bind(page.autoRenewBox, 'click', (e: MouseEvent) => { e.stopPropagation() - page.toggleAutoRenew.click() + if (!this.accountDisabled) page.toggleAutoRenew.click() }) page.penaltyComps.textContent = String(xc.auth.penaltyComps) @@ -173,7 +180,7 @@ export default class DexSettingsPage extends BasePage { }, this.host) // forms.bind(page.bondDetailsForm, page.updateBondOptionsConfirm, () => this.updateBondOptions()) - forms.bind(page.disableAccountForm, page.disableAccountConfirm, () => this.disableAccount()) + forms.bind(page.disableAccountForm, page.disableAccountConfirm, () => this.toggleAccountStatus(true)) Doc.bind(page.forms, 'mousedown', (e: MouseEvent) => { if (!Doc.mouseInElement(e, this.currentForm)) { this.closePopups() } @@ -321,21 +328,35 @@ export default class DexSettingsPage extends BasePage { Doc.hide(page.forms) } - // disableAccount disables the account associated with the provided host. - async disableAccount () { + // toggleAccountStatus enables or disables the account associated with the + // provided host. + async toggleAccountStatus (disable:boolean) { const page = this.page - const host = page.disableAccountHost.textContent - const req = { host, disable: true } + Doc.hide(page.errMsg) + let host: string|null = this.host + if (disable) host = page.disableAccountHost.textContent + const req = { host, disable: disable } const loaded = app().loading(this.body) const res = await postJSON('/api/toggleaccountstatus', req) loaded() if (!app().checkResponse(res)) { - page.disableAccountErr.textContent = res.msg - Doc.show(page.disableAccountErr) + if (disable) { + page.disableAccountErr.textContent = res.msg + Doc.show(page.disableAccountErr) + } else { + page.errMsg.textContent = res.msg + Doc.show(page.errMsg) + } return } - Doc.hide(page.forms) - window.location.assign('/settings') + if (disable) { + this.page.toggleAccountStatusBtn.textContent = intl.prep(intl.ID_ENABLE_ACCOUNT) + Doc.hide(page.forms) + } else { + this.page.toggleAccountStatusBtn.textContent = intl.prep(intl.ID_DISABLE_ACCOUNT) + } + this.accountDisabled = disable + window.location.assign(`/dexsettings/${host}`) } async prepareAccountDisable (disableAccountForm: HTMLElement) { @@ -402,7 +423,8 @@ export default class DexSettingsPage extends BasePage { break case ConnectionStatus.Disconnected: displayIcons(false) - page.connectionStatus.textContent = intl.prep(intl.ID_DISCONNECTED) + if (this.accountDisabled) page.connectionStatus.textContent = intl.prep(intl.ID_ACCOUNT_DISABLED_MSG) + else page.connectionStatus.textContent = intl.prep(intl.ID_DISCONNECTED) break case ConnectionStatus.InvalidCert: displayIcons(false) diff --git a/client/webserver/site/src/js/locales.ts b/client/webserver/site/src/js/locales.ts index b8c2520bfc..471f193dd9 100644 --- a/client/webserver/site/src/js/locales.ts +++ b/client/webserver/site/src/js/locales.ts @@ -195,6 +195,10 @@ export const ID_PENDING = 'PENDING' export const ID_COMPLETE = 'COMPLETE' export const ID_ARCHIVED_SETTINGS = 'ARCHIVED_SETTINGS' export const ID_NO_CODE_PROVIDED = 'NO_CODE_PROVIDED' +export const ID_ENABLE_ACCOUNT = 'ENABLE_ACCOUNT' +export const ID_DISABLE_ACCOUNT = 'DISABLE_ACCOUNT' +export const ID_ACCOUNT_DISABLED_MSG = 'ACCOUNT_DISABLED_MSG' +export const ID_DEX_DISABLED_MSG = 'DEX_DISABLED_MSG' let locale: Locale diff --git a/client/webserver/site/src/js/markets.ts b/client/webserver/site/src/js/markets.ts index a5ac72cd61..487e26b1fb 100644 --- a/client/webserver/site/src/js/markets.ts +++ b/client/webserver/site/src/js/markets.ts @@ -1092,7 +1092,9 @@ export default class MarketsPage extends BasePage { // exchange data, so just put up a message and wait for the connection to be // established, at which time handleConnNote will refresh and reload. if (!dex || !dex.markets || dex.connectionStatus !== ConnectionStatus.Connected) { - page.chartErrMsg.textContent = intl.prep(intl.ID_CONNECTION_FAILED) + let errMsg = intl.prep(intl.ID_CONNECTION_FAILED) + if (dex.disabled) errMsg = intl.prep(intl.ID_DEX_DISABLED_MSG) + page.chartErrMsg.textContent = errMsg Doc.show(page.chartErrMsg) return } diff --git a/client/webserver/site/src/js/registry.ts b/client/webserver/site/src/js/registry.ts index 1974d98c6e..99231d529e 100644 --- a/client/webserver/site/src/js/registry.ts +++ b/client/webserver/site/src/js/registry.ts @@ -63,6 +63,7 @@ export interface Exchange { candleDurs: string[] maxScore: number penaltyThreshold: number + disabled:boolean } export interface Candle { From 5e1dc4d7b7def6ffa2e69a5a1612223afe267b79 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Wed, 4 Sep 2024 10:06:36 +0100 Subject: [PATCH 4/5] client: add test case for enabling a dex account Signed-off-by: Philemon Ukane --- client/core/account.go | 4 ++++ client/core/account_test.go | 15 +++++++++++---- client/webserver/site/src/html/dexsettings.tmpl | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/client/core/account.go b/client/core/account.go index c604c95c9b..167908b13f 100644 --- a/client/core/account.go +++ b/client/core/account.go @@ -62,6 +62,10 @@ func (c *Core) ToggleAccountStatus(pw []byte, addr string, disable bool) error { return newError(unknownDEXErr, "error retrieving dex conn: %w", err) } + if dc.acct.isDisabled() == disable { + return nil // no-op + } + if disable { // Check active orders or bonds. if dc.hasActiveOrders() { diff --git a/client/core/account_test.go b/client/core/account_test.go index 09a6aa1139..62f20bb4a6 100644 --- a/client/core/account_test.go +++ b/client/core/account_test.go @@ -64,7 +64,6 @@ func TestToggleAccountStatus(t *testing.T) { {}: {metaData: &db.OrderMetaData{Status: order.OrderStatusBooked}}, } - // TODO: Add test case for enable dex account tests := []struct { name, host string recryptErr, acctErr, disableAcctErr error @@ -72,9 +71,13 @@ func TestToggleAccountStatus(t *testing.T) { activeTrades map[order.OrderID]*trackedTrade errCode int }{{ - name: "ok", + name: "ok: disable account", host: tDexHost, wantDisable: true, + }, { + name: "ok: enable account", + host: tDexHost, + wantDisable: false, }, { name: "password error", host: tDexHost, @@ -143,8 +146,8 @@ func TestToggleAccountStatus(t *testing.T) { t.Fatalf("unexpected error for test %v: %v", test.name, err) } if test.wantDisable { - if _, found := tCore.conns[test.host]; found { - t.Fatal("found disabled account dex connection") + if dc, found := tCore.conns[test.host]; found && !dc.acct.isDisabled() { + t.Fatal("expected disabled dex account") } if rig.db.disabledHost == nil { t.Fatal("expected a disable dex server host") @@ -153,6 +156,10 @@ func TestToggleAccountStatus(t *testing.T) { t.Fatalf("expected db account to match test host, want: %v"+ " got: %v", test.host, *rig.db.disabledHost) } + } else { + if dc, found := tCore.conns[test.host]; found && dc.acct.isDisabled() { + t.Fatal("expected enabled dex account") + } } } } diff --git a/client/webserver/site/src/html/dexsettings.tmpl b/client/webserver/site/src/html/dexsettings.tmpl index 36eb61640f..acb38bc92d 100644 --- a/client/webserver/site/src/html/dexsettings.tmpl +++ b/client/webserver/site/src/html/dexsettings.tmpl @@ -152,4 +152,4 @@
{{template "bottom"}} -{{end}} \ No newline at end of file +{{end}} From 87d10479d9428d46471fee8ea787d6f067ff1016 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Tue, 10 Sep 2024 21:10:45 +0100 Subject: [PATCH 5/5] buck review changes Signed-off-by: Philemon Ukane --- client/core/account.go | 4 ++++ client/core/bond.go | 4 ++-- client/core/core.go | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/core/account.go b/client/core/account.go index 167908b13f..0ec2051a95 100644 --- a/client/core/account.go +++ b/client/core/account.go @@ -71,6 +71,10 @@ func (c *Core) ToggleAccountStatus(pw []byte, addr string, disable bool) error { if dc.hasActiveOrders() { return fmt.Errorf("cannot disable account with active orders") } + + if dc.hasUnspentBond() { + c.log.Warnf("Disabling dex server with unspent bonds. Bonds will be refunded when expired.") + } } err = c.db.ToggleAccountStatus(addr, disable) diff --git a/client/core/bond.go b/client/core/bond.go index b544d2c5ec..c42f5fe537 100644 --- a/client/core/bond.go +++ b/client/core/bond.go @@ -705,7 +705,7 @@ func (c *Core) rotateBonds(ctx context.Context) { // locked. However, we must refund bonds regardless. bondCfg := c.dexBondConfig(dc, now) - if len(bondCfg.bondAssets) == 0 { + if len(bondCfg.bondAssets) == 0 && !dc.acct.isDisabled() { if !dc.IsDown() && dc.config() != nil { dc.log.Meter("no-bond-assets", time.Minute*10).Warnf("Zero bond assets reported for apparently connected DCRDEX server") } @@ -723,7 +723,7 @@ func (c *Core) rotateBonds(ctx context.Context) { } if dc.acct.isDisabled() { - continue // we can only attempt bond refund(if any) for disabled accounts + continue // For disabled account, we should only bother about unspent bonds that might have been refunded by refundExpiredBonds above. } c.repostPendingBonds(dc, bondCfg, acctBondState, unlocked) diff --git a/client/core/core.go b/client/core/core.go index d5635c8a8f..ba537d10bd 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -5141,7 +5141,7 @@ func (c *Core) initializeDEXConnections(crypter encrypt.Crypter) { } if dc.acct.isDisabled() { - continue // we can only unlock the dex account to init the account ID. + continue // For disabled account, we only want dc.acct.unlock above to initialize the account ID. } // Unlock the bond wallet if a target tier is set.