Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mmui: Fix bugs related to cex connection and balance #3060

Merged
merged 1 commit into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 80 additions & 18 deletions client/cmd/testbinance/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"math"
"math/rand"
"net/http"
Expand Down Expand Up @@ -150,15 +151,50 @@ func makeCoinpapAsset(assetID uint32, symbol, name string) *fiatrates.Coinpaprik
}
}

// sendBalanceUpdateRequest sends a balance update request to the testbinance server
// running in another process.
func sendBalanceUpdateRequest(coin string, balanceUpdate float64) {
if coin == "" || balanceUpdate == 0 {
fmt.Printf("Invalid balance update request: coin = %q, balanceUpdate = %f\n", coin, balanceUpdate)
return
}

url := fmt.Sprintf("http://localhost:37346/testbinance/updatebalance?coin=%s&amt=%f",
coin, balanceUpdate)
resp, err := http.Get(url)
if err != nil {
log.Errorf("Error sending balance update request: %v", err)
return
}

defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
fmt.Println("Balance update request failed:", string(body))
return
}

fmt.Println("Balance update request sent")
}

func main() {
var logDebug, logTrace bool
var coin string
var balanceUpdate float64
flag.Float64Var(&walkingSpeedAdj, "walkspeed", 1.0, "scale the maximum walking speed. default scale of 1.0 is about 3%")
flag.Float64Var(&gapRange, "gaprange", 0.04, "a ratio of how much the gap can vary. default is 0.04 => 4%")
flag.BoolVar(&logDebug, "debug", false, "use debug logging")
flag.BoolVar(&logTrace, "trace", false, "use trace logging")
flag.BoolVar(&flappyWS, "flappyws", false, "periodically drop websocket clients and delete subscriptions")
flag.Float64Var(&balanceUpdate, "balupdate", 0, "update the balance of an asset on a testbinance server running as another process")
flag.StringVar(&coin, "coin", "", "coin for testbinance admin update")
flag.Parse()

if balanceUpdate != 0 {
sendBalanceUpdateRequest(coin, balanceUpdate)
return
}

switch {
case logTrace:
log = dex.StdOutLogger("TB", dex.LevelTrace)
Expand Down Expand Up @@ -312,10 +348,32 @@ func newFakeBinanceServer(ctx context.Context) (*fakeBinance, error) {

mux.Get("/ws/{listenKey}", f.handleAccountSubscription)
mux.Get("/stream", f.handleMarketStream)
mux.Route("/testbinance", func(r chi.Router) {
r.Get("/updatebalance", f.handleUpdateBalance)
})

return f, nil
}

func (f *fakeBinance) handleUpdateBalance(w http.ResponseWriter, r *http.Request) {
coin := r.URL.Query().Get("coin")
amtStr := r.URL.Query().Get("amt")
amt, err := strconv.ParseFloat(amtStr, 64)
if err != nil {
http.Error(w, fmt.Sprintf("invalid amt %q: %v", amtStr, err), http.StatusBadRequest)
return
}

balUpdate := f.updateBalance(coin, amt)
if balUpdate == nil {
http.Error(w, fmt.Sprintf("no balance to update for %q", coin), http.StatusBadRequest)
return
}

f.sendBalanceUpdates([]*bntypes.WSBalance{balUpdate})
w.WriteHeader(http.StatusOK)
}

func (f *fakeBinance) run(ctx context.Context) {
// Start a ticker to do book shuffles.

Expand Down Expand Up @@ -779,6 +837,27 @@ func (f *fakeBinance) handleGetDepositAddress(w http.ResponseWriter, r *http.Req
writeJSONWithStatus(w, resp, http.StatusOK)
}

func (f *fakeBinance) updateBalance(coin string, amt float64) *bntypes.WSBalance {
f.balancesMtx.Lock()
defer f.balancesMtx.Unlock()

var balUpdate *bntypes.WSBalance

for _, b := range f.balances {
if b.Asset == coin {
if amt+b.Free < 0 {
b.Free = 0
} else {
b.Free += amt
}
balUpdate = (*bntypes.WSBalance)(b)
break
}
}

return balUpdate
}

func (f *fakeBinance) handleWithdrawal(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
apiKey := extractAPIKey(r)
Expand Down Expand Up @@ -814,24 +893,7 @@ func (f *fakeBinance) handleWithdrawal(w http.ResponseWriter, r *http.Request) {
}
f.withdrawalHistoryMtx.Unlock()

var balUpdate *bntypes.WSBalance
debitBalance := func(coin string, amt float64) {
for _, b := range f.balances {
if b.Asset == coin {
if amt > b.Free {
b.Free = 0
} else {
b.Free -= amt
}
}
balUpdate = (*bntypes.WSBalance)(b)
}
}

f.balancesMtx.Lock()
debitBalance(coin, amt)
f.balancesMtx.Unlock()

balUpdate := f.updateBalance(coin, -amt)
f.sendBalanceUpdates([]*bntypes.WSBalance{balUpdate})

resp := struct {
Expand Down
4 changes: 2 additions & 2 deletions client/mm/libxc/binance.go
Original file line number Diff line number Diff line change
Expand Up @@ -663,11 +663,11 @@ func (bnc *binance) Connect(ctx context.Context) (*sync.WaitGroup, error) {
wg := new(sync.WaitGroup)

if err := bnc.getCoinInfo(ctx); err != nil {
return nil, fmt.Errorf("error getting coin info: %v", err)
return nil, fmt.Errorf("error getting coin info: %w", err)
}

if _, err := bnc.getMarkets(ctx); err != nil {
return nil, fmt.Errorf("error getting markets: %v", err)
return nil, fmt.Errorf("error getting markets: %w", err)
}

if err := bnc.setBalances(ctx); err != nil {
Expand Down
18 changes: 9 additions & 9 deletions client/mm/mm.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,21 +510,21 @@ func (m *MarketMaker) connectCEX(ctx context.Context, c *centralizedExchange) er
if !cm.On() {
c.connectErr = ""
if err := cm.ConnectOnce(ctx); err != nil {
c.connectErr = err.Error()
return fmt.Errorf("failed to connect to CEX: %v", err)
c.connectErr = core.UnwrapErr(err).Error()
return fmt.Errorf("failed to connect to CEX: %w", err)
}
mkts, err := c.Markets(ctx)
if err != nil {
// Probably can't get here if we didn't error on connect, but
// checking anyway.
c.connectErr = err.Error()
return fmt.Errorf("error refreshing markets: %v", err)
c.connectErr = core.UnwrapErr(err).Error()
return fmt.Errorf("error refreshing markets: %w", err)
}
c.mkts = mkts
bals, err := c.Balances(ctx)
if err != nil {
c.connectErr = err.Error()
return fmt.Errorf("error getting balances: %v", err)
c.connectErr = core.UnwrapErr(err).Error()
return fmt.Errorf("error getting balances: %w", err)
}
c.balances = bals
}
Expand Down Expand Up @@ -590,12 +590,12 @@ func (m *MarketMaker) loadCEX(ctx context.Context, cfg *CEXConfig) (*centralized
if err != nil {
m.log.Errorf("Failed to get markets for %s: %v", cfg.Name, err)
c.mkts = make(map[string]*libxc.Market)
c.connectErr = err.Error()
c.connectErr = core.UnwrapErr(err).Error()
}
if c.balances, err = c.Balances(ctx); err != nil {
m.log.Errorf("Failed to get balances for %s: %v", cfg.Name, err)
c.balances = make(map[uint32]*libxc.ExchangeBalance)
c.connectErr = err.Error()
c.connectErr = core.UnwrapErr(err).Error()
}
m.cexes[cfg.Name] = c
success = true
Expand Down Expand Up @@ -1390,7 +1390,7 @@ func (m *MarketMaker) connectedCEX(cexName string) (*centralizedExchange, error)

err := m.connectCEX(m.ctx, cex)
if err != nil {
return nil, fmt.Errorf("error connecting to CEX: %v", err)
return nil, fmt.Errorf("error connecting to CEX: %w", err)
}

return cex, nil
Expand Down
4 changes: 4 additions & 0 deletions client/webserver/jsintl.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ const (
idOrderReportTitle = "ORDER_REPORT_TITLE"
idCEXBalances = "CEX_BALANCES"
idCausesSelfMatch = "CAUSES_SELF_MATCH"
idCexNotConnected = "CEX_NOT_CONNECTED"
idRemovingBotConfig = "REMOVING_BOT_CONFIG"
)

var enUS = map[string]*intl.Translation{
Expand Down Expand Up @@ -433,6 +435,8 @@ var enUS = map[string]*intl.Translation{
idOrderReportTitle: {T: "{{ side }} orders report for epoch #{{ epochNum }}"},
idCEXBalances: {T: "{{ cexName }} Balances"},
idCausesSelfMatch: {T: "This order would cause a self-match"},
idCexNotConnected: {T: "{{ cexName }} not connected"},
idRemovingBotConfig: {T: "Are you sure you want to remove the config for the {{ baseSymbol }}-{{ quoteSymbol }} market on {{ host }}?"},
}

var ptBR = map[string]*intl.Translation{
Expand Down
1 change: 1 addition & 0 deletions client/webserver/locales/en-us.go
Original file line number Diff line number Diff line change
Expand Up @@ -678,4 +678,5 @@ var EnUS = map[string]*intl.Translation{
"Priority": {T: "Priority"},
"Wallet Balances": {T: "Wallet Balances"},
"Placements": {T: "Placements"},
"removing_bot_config": {T: "Removing Bot Config"},
}
16 changes: 16 additions & 0 deletions client/webserver/site/src/html/mm.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,12 @@
<button data-tmpl="reconfigureBttn">
<span class="ico-settings fs17"></span>
</button>
<button class="ms-1 danger" data-tmpl="removeBttn">
<span class="ico-cross fs17"></span>
</button>
</div>
</div>
<span data-tmpl="offError" class="fs16 px-3 text-center text-warning d-hide"></span>

{{- /* PROJECTED ALLOCATIONS */ -}}
<div class="flex-stretch-column px-3 pt-2 mt-2 border-top">
Expand Down Expand Up @@ -609,6 +613,18 @@
<form class="position-relative d-hide" id="orderReportForm" autocomplete="off">
{{template "orderReportForm"}}
</form>

<form class="position-relative mw-425 d-hide" id="confirmRemoveForm" autocomplete="off">
<div class="form-closer"><span class="ico-cross"></span></div>
<div>
<header>[[[removing_bot_config]]]</header>
<div>
<span id="confirmRemoveCfgMsg"></span>
</div>
<button id="confirmRemoveConfigBttn" type="button" class="feature mt-2">Confirm</button>
<div id="removeCfgErr" class="fs15 text-center d-hide text-danger text-break"></div>
</div>
</form>
</div> {{- /* END FORMS */ -}}
</div>
{{template "bottom"}}
Expand Down
11 changes: 11 additions & 0 deletions client/webserver/site/src/js/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,17 @@ export default class Doc {
for (const el of els) el.classList.remove('d-hide')
}

/*
* showTemporarily shows the specified elements for the specified time, then
* hides it again.
*/
static showTemporarily (timeout: number, ...els: Element[]) {
this.show(...els)
setTimeout(() => {
this.hide(...els)
}, timeout)
}

/*
* show or hide the specified elements, based on value of the truthiness of
* vis.
Expand Down
12 changes: 5 additions & 7 deletions client/webserver/site/src/js/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2151,14 +2151,13 @@ export class TokenApprovalForm {
export class CEXConfigurationForm {
form: PageElement
page: Record<string, PageElement>
success: (cexName: string) => void
updated: (cexName: string, success: boolean) => void
cexName: string

constructor (form: PageElement, success: (cexName: string) => void) {
constructor (form: PageElement, updated: (cexName: string, success: boolean) => void) {
this.form = form
this.success = success
this.updated = updated
this.page = Doc.parseTemplate(form)

Doc.bind(this.page.cexSubmit, 'click', () => this.submit())
}

Expand Down Expand Up @@ -2202,12 +2201,11 @@ export class CEXConfigurationForm {
apiSecret: apiSecret
})
if (!app().checkResponse(res)) throw res
await app().fetchMMStatus()
this.success(cexName)
this.updated(cexName, true)
} catch (e) {
Doc.show(page.cexFormErr)
page.cexFormErr.textContent = intl.prep(intl.ID_API_ERROR, { msg: e.msg ?? String(e) })
return
this.updated(cexName, false)
} finally {
loaded()
}
Expand Down
2 changes: 2 additions & 0 deletions client/webserver/site/src/js/locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ export const ID_CEX_TRADE_ERROR = 'CEX_TRADE_ERROR'
export const ID_ORDER_REPORT_TITLE = 'ORDER_REPORT_TITLE'
export const ID_CEX_BALANCES = 'CEX_BALANCES'
export const ID_CAUSES_SELF_MATCH = 'CAUSES_SELF_MATCH'
export const ID_CEX_NOT_CONNECTED = 'CEX_NOT_CONNECTED'
export const ID_REMOVING_BOT_CONFIG = 'REMOVING_BOT_CONFIG'

let locale: Locale

Expand Down
Loading
Loading