Skip to content

Commit

Permalink
More review fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
martonp committed Sep 12, 2023
1 parent 08a73db commit b57a3aa
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 143 deletions.
18 changes: 15 additions & 3 deletions client/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ type LogConfig struct {
LocalLogs bool `long:"loglocal" description:"Use local time zone time stamps in log entries."`
}

// MMConfig encapsulates the settings specific to market making.
type MMConfig struct {
BotConfigPath string `long:"botConfigPath"`
}

// Config is the common application configuration definition. This composite
// struct captures the configuration needed for core and both web and rpc
// servers, as well as some application-level directives.
Expand All @@ -129,6 +134,7 @@ type Config struct {
RPCConfig
WebConfig
LogConfig
MMConfig
// AppData and ConfigPath should be parsed from the command-line,
// as it makes no sense to set these in the config file itself. If no values
// are assigned, defaults will be used.
Expand Down Expand Up @@ -166,12 +172,9 @@ func (cfg *Config) Web(c *core.Core, mm *mm.MarketMaker, log dex.Logger, utc boo
keyFile = filepath.Join(cfg.AppData, "web.key")
}

_, _, mmCfgPath := setNet(cfg.AppData, cfg.Net.String())

return &webserver.Config{
Core: c,
MarketMaker: mm,
MMCfgPath: mmCfgPath,
Addr: cfg.WebAddr,
CustomSiteDir: cfg.SiteDir,
Logger: log,
Expand Down Expand Up @@ -205,6 +208,15 @@ func (cfg *Config) Core(log dex.Logger) *core.Config {
}
}

// MarketMakerConfigPath returns the path to the market maker config file.
func (cfg *Config) MarketMakerConfigPath() string {
if cfg.MMConfig.BotConfigPath != "" {
return cfg.MMConfig.BotConfigPath
}
_, _, mmCfgPath := setNet(cfg.AppData, cfg.Net.String())
return mmCfgPath
}

var DefaultConfig = Config{
AppData: defaultApplicationDirectory,
ConfigPath: defaultConfigPath,
Expand Down
2 changes: 1 addition & 1 deletion client/cmd/dexc-desktop/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func mainCore() error {
if cfg.Experimental {
// TODO: on shutdown, stop market making and wait for trades to be
// canceled.
marketMaker, err = mm.NewMarketMaker(clientCore, logMaker.Logger("MM"))
marketMaker, err = mm.NewMarketMaker(clientCore, cfg.MarketMakerConfigPath(), logMaker.Logger("MM"))
if err != nil {
return fmt.Errorf("error creating market maker: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion client/cmd/dexc-desktop/app_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ func mainCore() error {
if cfg.Experimental {
// TODO: on shutdown, stop market making and wait for trades to be
// canceled.
marketMaker, err = mm.NewMarketMaker(clientCore, logMaker.Logger("MM"))
marketMaker, err = mm.NewMarketMaker(clientCore, cfg.MarketMakerConfigPath(), logMaker.Logger("MM"))
if err != nil {
return fmt.Errorf("error creating market maker: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion client/cmd/dexc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func runCore(cfg *app.Config) error {
if cfg.Experimental {
// TODO: on shutdown, stop market making and wait for trades to be
// canceled.
marketMaker, err = mm.NewMarketMaker(clientCore, logMaker.Logger("MM"))
marketMaker, err = mm.NewMarketMaker(clientCore, cfg.MarketMakerConfigPath(), logMaker.Logger("MM"))
if err != nil {
return fmt.Errorf("error creating market maker: %w", err)
}
Expand Down
6 changes: 6 additions & 0 deletions client/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -10335,6 +10335,12 @@ func (c *Core) FiatRateSources() map[string]bool {
return rateSources
}

// FiatConversionRates are the currently cached fiat conversion rates. Must have
// 1 or more fiat rate sources enabled.
func (c *Core) FiatConversionRates() map[uint32]float64 {
return c.fiatConversions()
}

// fiatConversions returns fiat rate for all supported assets that have a
// wallet.
func (c *Core) fiatConversions() map[uint32]float64 {
Expand Down
120 changes: 109 additions & 11 deletions client/mm/mm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ package mm
import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"os"
"sync"
"sync/atomic"

Expand Down Expand Up @@ -38,6 +40,7 @@ type clientCore interface {
Login(pw []byte) error
OpenWallet(assetID uint32, appPW []byte) error
Broadcast(core.Notification)
FiatConversionRates() map[uint32]float64
}

var _ clientCore = (*core.Core)(nil)
Expand Down Expand Up @@ -155,34 +158,38 @@ type MarketMaker struct {
die context.CancelFunc
running atomic.Bool
log dex.Logger
dir string
core clientCore
doNotKillWhenBotsStop bool // used for testing
botBalances map[string]*botBalances
// syncedOracle is only available while the MarketMaker is running.
cfgPath string
// syncedOracle is only available while the MarketMaker is running. It
// periodically refreshes the prices for the markets that have bots
// running on them.
syncedOracleMtx sync.RWMutex
syncedOracle *priceOracle

// unsyncedOracle is always available and can be used to query prices on
// all markets. It does not periodically refresh the prices, and queries
// them on demand.
unsyncedOracle *priceOracle

runningBotsMtx sync.RWMutex
runningBots map[MarketWithHost]interface{}

noteMtx sync.RWMutex
noteChans map[uint64]chan core.Notification

ordersMtx sync.RWMutex
orders map[order.OrderID]*orderInfo
}

// NewMarketMaker creates a new MarketMaker.
func NewMarketMaker(c clientCore, log dex.Logger) (*MarketMaker, error) {
func NewMarketMaker(c clientCore, cfgPath string, log dex.Logger) (*MarketMaker, error) {
return &MarketMaker{
core: c,
log: log,
cfgPath: cfgPath,
running: atomic.Bool{},
orders: make(map[order.OrderID]*orderInfo),
runningBots: make(map[MarketWithHost]interface{}),
noteChans: make(map[uint64]chan core.Notification),
unsyncedOracle: newUnsyncedPriceOracle(log),
}, nil
}
Expand Down Expand Up @@ -275,18 +282,18 @@ func (m *MarketMaker) markBotAsRunning(mkt MarketWithHost, running bool) {
// MarketReport returns information about the oracle rates on a market
// pair and the fiat rates of the base and quote assets.
func (m *MarketMaker) MarketReport(base, quote uint32) (*MarketReport, error) {
user := m.core.User()
baseFiatRate := user.FiatRates[base]
quoteFiatRate := user.FiatRates[quote]
fiatRates := m.core.FiatConversionRates()
baseFiatRate := fiatRates[base]
quoteFiatRate := fiatRates[quote]

m.syncedOracleMtx.RLock()
if m.syncedOracle != nil {
price, oracles, err := m.syncedOracle.getOracleInfo(base, quote)
m.syncedOracleMtx.RUnlock()
if err != nil && !errors.Is(err, errUnsyncedMarket) {
m.log.Errorf("failed to get oracle info for market %d-%d: %v", base, quote, err)
}
if err == nil {
m.syncedOracleMtx.RUnlock()
return &MarketReport{
Price: price,
Oracles: oracles,
Expand Down Expand Up @@ -820,10 +827,18 @@ func (m *MarketMaker) handleNotification(n core.Notification) {
}

// Run starts the MarketMaker. There can only be one BotConfig per dex market.
func (m *MarketMaker) Run(ctx context.Context, cfgs []*BotConfig, pw []byte) error {
func (m *MarketMaker) Run(ctx context.Context, pw []byte, alternateConfigPath *string) error {
if !m.running.CompareAndSwap(false, true) {
return errors.New("market making is already running")
}
path := m.cfgPath
if alternateConfigPath != nil {
path = *alternateConfigPath
}
cfgs, err := getMarketMakingConfig(path)
if err != nil {
return fmt.Errorf("error getting market making config: %v", err)
}

var startedMarketMaking bool
defer func() {
Expand Down Expand Up @@ -926,6 +941,89 @@ func (m *MarketMaker) Run(ctx context.Context, cfgs []*BotConfig, pw []byte) err
return nil
}

func getMarketMakingConfig(path string) ([]*BotConfig, error) {
cfg := []*BotConfig{}
if path == "" {
return cfg, nil
}
data, err := os.ReadFile(path)
if err == nil {
return cfg, json.Unmarshal(data, &cfg)
}
if os.IsNotExist(err) {
return cfg, nil
}
return nil, fmt.Errorf("error reading file: %w", err)
}

// GetMarketMakingConfig returns the market making config.
func (m *MarketMaker) GetMarketMakingConfig() ([]*BotConfig, error) {
return getMarketMakingConfig(m.cfgPath)
}

// UpdateMarketMakingConfig updates the market making config for one of the
// bots.
func (m *MarketMaker) UpdateMarketMakingConfig(updatedCfg *BotConfig) ([]*BotConfig, error) {
cfg, err := m.GetMarketMakingConfig()
if err != nil {
return nil, fmt.Errorf("error getting market making config: %v", err)
}

var updated bool
for i, c := range cfg {
if c.Host == updatedCfg.Host && c.QuoteAsset == updatedCfg.QuoteAsset && c.BaseAsset == updatedCfg.BaseAsset {
cfg[i] = updatedCfg
updated = true
break
}
}
if !updated {
cfg = append(cfg, updatedCfg)
}

data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return nil, fmt.Errorf("error marshalling market making config: %v", err)
}

err = os.WriteFile(m.cfgPath, data, 0644)
if err != nil {
return nil, fmt.Errorf("error writing market making config: %v", err)
}
return cfg, nil
}

// RemoveConfig removes a bot config from the market making config.
func (m *MarketMaker) RemoveConfig(host string, baseID, quoteID uint32) ([]*BotConfig, error) {
cfg, err := m.GetMarketMakingConfig()
if err != nil {
return nil, fmt.Errorf("error getting market making config: %v", err)
}

var updated bool
for i, c := range cfg {
if c.Host == host && c.QuoteAsset == quoteID && c.BaseAsset == baseID {
cfg = append(cfg[:i], cfg[i+1:]...)
updated = true
break
}
}
if !updated {
return nil, fmt.Errorf("config not found")
}

data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return nil, fmt.Errorf("error marshalling market making config: %v", err)
}

err = os.WriteFile(m.cfgPath, data, 0644)
if err != nil {
return nil, fmt.Errorf("error writing market making config: %v", err)
}
return cfg, nil
}

// Stop stops the MarketMaker.
func (m *MarketMaker) Stop() {
if m.die != nil {
Expand Down
Loading

0 comments on commit b57a3aa

Please sign in to comment.