decred.org/dcrdex@v1.0.5/server/market/market.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package market 5 6 import ( 7 "context" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "math" 12 "strings" 13 "sync" 14 "sync/atomic" 15 "time" 16 17 "decred.org/dcrdex/dex" 18 "decred.org/dcrdex/dex/calc" 19 "decred.org/dcrdex/dex/msgjson" 20 "decred.org/dcrdex/dex/order" 21 "decred.org/dcrdex/dex/ws" 22 "decred.org/dcrdex/server/account" 23 "decred.org/dcrdex/server/asset" 24 "decred.org/dcrdex/server/auth" 25 "decred.org/dcrdex/server/book" 26 "decred.org/dcrdex/server/coinlock" 27 "decred.org/dcrdex/server/comms" 28 "decred.org/dcrdex/server/db" 29 "decred.org/dcrdex/server/matcher" 30 ) 31 32 // Error is just a basic error. 33 type Error string 34 35 // Error satisfies the error interface. 36 func (e Error) Error() string { 37 return string(e) 38 } 39 40 const ( 41 ErrMarketNotRunning = Error("market not running") 42 ErrInvalidOrder = Error("order failed validation") 43 ErrInvalidRate = Error("limit order rate too low") 44 ErrInvalidCommitment = Error("order commitment invalid") 45 ErrEpochMissed = Error("order unexpectedly missed its intended epoch") 46 ErrDuplicateOrder = Error("order already in epoch") // maybe remove since this is ill defined 47 ErrQuantityTooHigh = Error("order quantity exceeds user limit") 48 ErrDuplicateCancelOrder = Error("equivalent cancel order already in epoch") 49 ErrTooManyCancelOrders = Error("too many cancel orders in current epoch") 50 ErrCancelNotPermitted = Error("cancel order account does not match targeted order account") 51 ErrTargetNotActive = Error("target order not active on this market") 52 ErrTargetNotCancelable = Error("targeted order is not a limit order with standing time-in-force") 53 ErrSuspendedAccount = Error("suspended account") 54 ErrMalformedOrderResponse = Error("malformed order response") 55 ErrInternalServer = Error("internal server error") 56 ) 57 58 // Swapper coordinates atomic swaps for one or more matchsets. 59 type Swapper interface { 60 Negotiate(matchSets []*order.MatchSet) 61 CheckUnspent(ctx context.Context, asset uint32, coinID []byte) error 62 ChainsSynced(base, quote uint32) (bool, error) 63 } 64 65 type DataCollector interface { 66 ReportEpoch(base, quote uint32, epochIdx uint64, stats *matcher.MatchCycleStats) (*msgjson.Spot, error) 67 } 68 69 // FeeFetcher is a fee fetcher for fetching fees. Fees are fickle, so fetch fees 70 // with FeeFetcher fairly frequently. 71 type FeeFetcher interface { 72 FeeRate(context.Context) uint64 73 SwapFeeRate(context.Context) uint64 74 LastRate() uint64 75 MaxFeeRate() uint64 76 } 77 78 // Balancer provides a method to check that an account on an account-based 79 // asset has sufficient balance. 80 type Balancer interface { 81 // CheckBalance checks that the address's account has sufficient balance to 82 // trade the outgoing number of lots (totaling qty) and incoming number of 83 // redeems. 84 CheckBalance(acctAddr string, assetID, redeemAssetID uint32, qty, lots uint64, redeems int) bool 85 } 86 87 // Config is the Market configuration. 88 type Config struct { 89 MarketInfo *dex.MarketInfo 90 Storage Storage 91 Swapper Swapper 92 AuthManager AuthManager 93 FeeFetcherBase FeeFetcher 94 CoinLockerBase coinlock.CoinLocker 95 FeeFetcherQuote FeeFetcher 96 CoinLockerQuote coinlock.CoinLocker 97 DataCollector DataCollector 98 Balancer Balancer 99 CheckParcelLimit func(user account.AccountID, calcParcels MarketParcelCalculator) bool 100 MinimumRate uint64 101 } 102 103 // Market is the market manager. It should not be overly involved with details 104 // of accounts and authentication. Via the account package it should request 105 // account status with new orders, verification of order signatures. The Market 106 // should also perform various account package callbacks such as order status 107 // updates so that the account package code can keep various data up-to-date, 108 // including order status, history, cancellation statistics, etc. 109 // 110 // The Market performs the following: 111 // 1. Receive and validate new order data (amounts vs. lot size, check fees, 112 // utxos, sufficient market buy buffer, etc.). 113 // 2. Put incoming orders into the current epoch queue. 114 // 3. Maintain an order book, which must also implement matcher.Booker. 115 // 4. Initiate order matching with matcher.Match(book, currentQueue) 116 // 5. During and/or after matching: 117 // * update the book (remove orders, add new standing orders, etc.) 118 // * retire/archive the epoch queue 119 // * publish the matches (and order book changes?) 120 // * initiate swaps for each match (possibly groups of related matches) 121 // 6. Cycle the epochs. 122 // 7. Record all events with the archivist. 123 type Market struct { 124 marketInfo *dex.MarketInfo 125 126 tasks sync.WaitGroup // for lazy asynchronous tasks e.g. revoke ntfns 127 128 // Communications. 129 orderRouter chan *orderUpdateSignal // incoming orders, via SubmitOrderAsync 130 131 orderFeedMtx sync.RWMutex // guards orderFeeds and running 132 orderFeeds []chan *updateSignal // all outgoing notification consumers 133 134 runMtx sync.RWMutex 135 running chan struct{} // closed when running (accepting new orders) 136 up uint32 // Run is called, either waiting for first epoch or running 137 138 bookMtx sync.Mutex // guards book and bookEpochIdx 139 book *book.Book 140 bookEpochIdx int64 // next epoch from the point of view of the book 141 settling map[order.OrderID]uint64 142 143 epochMtx sync.RWMutex 144 startEpochIdx int64 145 activeEpochIdx int64 146 suspendEpochIdx int64 147 persistBook bool 148 epochCommitments map[order.Commitment]order.OrderID 149 epochOrders map[order.OrderID]order.Order 150 151 matcher *matcher.Matcher 152 swapper Swapper 153 auth AuthManager 154 155 feeScalesMtx sync.RWMutex 156 feeScales struct { 157 base float64 158 quote float64 159 } 160 161 coinLockerBase coinlock.CoinLocker 162 coinLockerQuote coinlock.CoinLocker 163 164 baseFeeFetcher FeeFetcher 165 quoteFeeFetcher FeeFetcher 166 167 // Persistent data storage 168 storage Storage 169 170 // Data API 171 dataCollector DataCollector 172 lastRate uint64 173 174 checkParcelLimit func(user account.AccountID, calcParcels MarketParcelCalculator) bool 175 176 minimumRate uint64 177 } 178 179 // Storage is the DB interface required by Market. 180 type Storage interface { 181 db.OrderArchiver 182 LastErr() error 183 Fatal() <-chan struct{} 184 Close() error 185 InsertEpoch(ed *db.EpochResults) error 186 LastEpochRate(base, quote uint32) (uint64, error) 187 MarketMatches(base, quote uint32) ([]*db.MatchDataWithCoins, error) 188 InsertMatch(match *order.Match) error 189 } 190 191 // NewMarket creates a new Market for the provided base and quote assets, with 192 // an epoch cycling at given duration in milliseconds. 193 func NewMarket(cfg *Config) (*Market, error) { 194 // Make sure the DEXArchivist is healthy before taking orders. 195 storage, mktInfo, swapper := cfg.Storage, cfg.MarketInfo, cfg.Swapper 196 if err := storage.LastErr(); err != nil { 197 return nil, err 198 } 199 200 // Load existing book orders from the DB. 201 base, quote := mktInfo.Base, mktInfo.Quote 202 203 bookOrders, err := storage.BookOrders(base, quote) 204 if err != nil { 205 return nil, err 206 } 207 log.Infof("Loaded %d stored book orders.", len(bookOrders)) 208 209 baseIsAcctBased := cfg.CoinLockerBase == nil 210 quoteIsAcctBased := cfg.CoinLockerQuote == nil 211 212 // Put the book orders in a map so orders that no longer have funding coins 213 // can be removed easily. 214 bookOrdersByID := make(map[order.OrderID]*order.LimitOrder, len(bookOrders)) 215 for _, lo := range bookOrders { 216 // Limit order amount requirements are simple unlike market buys. 217 if lo.Quantity%mktInfo.LotSize != 0 || lo.FillAmt%mktInfo.LotSize != 0 { 218 // To change market configuration, the operator should suspended the 219 // market with persist=false, but that may not have happened, or 220 // maybe a revoke failed. 221 log.Errorf("Not rebooking order %v with amount (%v/%v) incompatible with current lot size (%v)", 222 lo.ID(), lo.FillAmt, lo.Quantity, mktInfo.LotSize) 223 // Revoke the order, but do not count this against the user. 224 if _, _, err = storage.RevokeOrderUncounted(lo); err != nil { 225 log.Errorf("Failed to revoke order %v: %v", lo, err) 226 // But still not added back on the book. 227 } 228 continue 229 } 230 bookOrdersByID[lo.ID()] = lo 231 } 232 233 // "execute" any epoch orders in DB that may be left over from unclean 234 // shutdown. Whatever epoch they were in will not be seen again. 235 epochOrders, err := storage.EpochOrders(base, quote) 236 if err != nil { 237 return nil, err 238 } 239 for _, ord := range epochOrders { 240 oid := ord.ID() 241 log.Infof("Dropping old epoch order %v", oid) 242 if co, ok := ord.(*order.CancelOrder); ok { 243 if err := storage.FailCancelOrder(co); err != nil { 244 log.Errorf("Failed to set orphaned epoch cancel order %v as executed: %v", oid, err) 245 } 246 continue 247 } 248 if err := storage.ExecuteOrder(ord); err != nil { 249 log.Errorf("Failed to set orphaned epoch trade order %v as executed: %v", oid, err) 250 } 251 } 252 253 // Set up tracking. Which of these are actually used depend on whether the 254 // assets are account- or utxo-based. 255 // utxo-based 256 var baseCoins, quoteCoins map[order.OrderID][]order.CoinID 257 var missingCoinFails map[order.OrderID]struct{} 258 // account-based 259 var quoteAcctStats, baseAcctStats accountCounter 260 var failedBaseAccts, failedQuoteAccts map[string]bool 261 var failedAcctOrders map[order.OrderID]struct{} 262 var acctTracking book.AccountTracking 263 264 if baseIsAcctBased { 265 acctTracking |= book.AccountTrackingBase 266 baseAcctStats = make(accountCounter) 267 failedBaseAccts = make(map[string]bool) 268 failedAcctOrders = make(map[order.OrderID]struct{}) 269 } else { 270 baseCoins = make(map[order.OrderID][]order.CoinID) 271 missingCoinFails = make(map[order.OrderID]struct{}) 272 } 273 274 if quoteIsAcctBased { 275 acctTracking |= book.AccountTrackingQuote 276 quoteAcctStats = make(accountCounter) 277 failedQuoteAccts = make(map[string]bool) 278 if failedAcctOrders == nil { 279 failedAcctOrders = make(map[order.OrderID]struct{}) 280 } 281 } else { 282 quoteCoins = make(map[order.OrderID][]order.CoinID) 283 if missingCoinFails == nil { 284 missingCoinFails = make(map[order.OrderID]struct{}) 285 } 286 } 287 288 ordersLoop: 289 for id, lo := range bookOrdersByID { 290 if lo.FillAmt > 0 { 291 // Order already matched with another trade, so it is expected that 292 // the funding coins are spent in a swap. 293 // 294 // In general, our position is that the server is not ultimately 295 // responsible for verifying that all orders have locked coins since 296 // the client will be penalized if they cannot complete the swap. 297 // The least the server can do is ensure funding coins for NEW 298 // orders are unspent and owned by the user. 299 300 // On to the next order. Do not lock coins that are spent or should 301 // be spent in a swap contract. 302 continue 303 } 304 305 // Verify all funding coins for this order. 306 assetID := quote 307 if lo.Sell { 308 assetID = base 309 } 310 for i := range lo.Coins { 311 err = swapper.CheckUnspent(context.Background(), assetID, lo.Coins[i]) // no timeout 312 if err == nil { 313 continue 314 } 315 316 if errors.Is(err, asset.CoinNotFoundError) { 317 // spent, exclude this order 318 log.Warnf("Coin %s not unspent for unfilled order %v. "+ 319 "Revoking the order.", fmtCoinID(assetID, lo.Coins[i]), lo) 320 } else { 321 // other failure (coinID decode, RPC, etc.) 322 return nil, fmt.Errorf("unexpected error checking coinID %v for order %v: %w", 323 lo.Coins[i], lo, err) 324 // NOTE: This does not revoke orders from storage since this is 325 // likely to be a configuration or node issue. 326 } 327 328 delete(bookOrdersByID, id) 329 // Revoke the order, but do not count this against the user. 330 if _, _, err = storage.RevokeOrderUncounted(lo); err != nil { 331 log.Errorf("Failed to revoke order %v: %v", lo, err) 332 } 333 // No penalization here presently since the market was down, but if 334 // a suspend message with persist=true was sent, the users should 335 // have kept their coins locked. (TODO) 336 continue ordersLoop 337 } 338 339 if baseIsAcctBased { 340 var addr string 341 var qty, lots uint64 342 var redeems int 343 if lo.Sell { 344 // address is zeroth coin 345 if len(lo.Coins) != 1 { 346 log.Errorf("rejecting account-based-base-asset order %s that has no coins ¯\\_(ツ)_/¯", lo.ID()) 347 continue ordersLoop 348 } 349 addr = string(lo.Coins[0]) 350 qty = lo.Quantity 351 lots = qty / mktInfo.LotSize 352 } else { 353 addr = lo.Address 354 redeems = int((lo.Quantity - lo.FillAmt) / mktInfo.LotSize) 355 } 356 baseAcctStats.add(addr, qty, lots, redeems) 357 } else if lo.Sell { 358 baseCoins[id] = lo.Coins 359 } 360 361 if quoteIsAcctBased { 362 var addr string 363 var qty, lots uint64 364 var redeems int 365 if lo.Sell { // sell base => redeem acct-based quote 366 addr = lo.Address 367 redeems = int((lo.Quantity - lo.FillAmt) / mktInfo.LotSize) 368 } else { // buy base => offer acct-based quote 369 // address is zeroth coin 370 if len(lo.Coins) != 1 { 371 log.Errorf("rejecting account-based-base-asset order %s that has no coins ¯\\_(ツ)_/¯", lo.ID()) 372 continue ordersLoop 373 } 374 addr = string(lo.Coins[0]) 375 lots = lo.Quantity / mktInfo.LotSize 376 qty = calc.BaseToQuote(lo.Rate, lo.Quantity) 377 } 378 quoteAcctStats.add(addr, qty, lots, redeems) 379 } else if !lo.Sell { 380 quoteCoins[id] = lo.Coins 381 } 382 } 383 384 if baseIsAcctBased { 385 log.Debugf("Checking %d base asset (%d) balances.", len(baseAcctStats), base) 386 for acctAddr, stats := range baseAcctStats { 387 if !cfg.Balancer.CheckBalance(acctAddr, mktInfo.Base, mktInfo.Quote, stats.qty, stats.lots, stats.redeems) { 388 log.Info("%s base asset account failed the startup balance check on the %s market", acctAddr, mktInfo.Name) 389 failedBaseAccts[acctAddr] = true 390 } 391 } 392 } else { 393 log.Debugf("Locking %d base asset (%d) coins.", len(baseCoins), base) 394 if log.Level() <= dex.LevelTrace { 395 for oid, coins := range baseCoins { 396 log.Tracef(" - order %v: %v", oid, coins) 397 } 398 } 399 for oid := range cfg.CoinLockerBase.LockCoins(baseCoins) { 400 missingCoinFails[oid] = struct{}{} 401 } 402 } 403 404 if quoteIsAcctBased { 405 log.Debugf("Checking %d quote asset (%d) balances.", len(quoteAcctStats), quote) 406 for acctAddr, stats := range quoteAcctStats { // quoteAcctStats is nil for utxo-based quote assets 407 if !cfg.Balancer.CheckBalance(acctAddr, mktInfo.Quote, mktInfo.Base, stats.qty, stats.lots, stats.redeems) { 408 log.Errorf("%s quote asset account failed the startup balance check on the %s market", acctAddr, mktInfo.Name) 409 failedQuoteAccts[acctAddr] = true 410 } 411 } 412 } else { 413 log.Debugf("Locking %d quote asset (%d) coins.", len(quoteCoins), quote) 414 if log.Level() <= dex.LevelTrace { 415 for oid, coins := range quoteCoins { 416 log.Tracef(" - order %v: %v", oid, coins) 417 } 418 } 419 for oid := range cfg.CoinLockerQuote.LockCoins(quoteCoins) { 420 missingCoinFails[oid] = struct{}{} 421 } 422 } 423 424 for oid := range missingCoinFails { 425 log.Warnf("Revoking book order %v with already locked coins.", oid) 426 bad := bookOrdersByID[oid] 427 delete(bookOrdersByID, oid) 428 // Revoke the order, but do not count this against the user. 429 if _, _, err = storage.RevokeOrderUncounted(bad); err != nil { 430 log.Errorf("Failed to revoke order %v: %v", bad, err) 431 // But still not added back on the book. 432 } 433 } 434 435 Book := book.New(mktInfo.LotSize, acctTracking) 436 for _, lo := range bookOrdersByID { 437 // Catch account-based asset low-balance rejections here. 438 if baseIsAcctBased && failedBaseAccts[lo.BaseAccount()] { 439 failedAcctOrders[lo.ID()] = struct{}{} 440 log.Warnf("Skipping insert of order %s into %s book because base asset "+ 441 "account failed the balance check", lo.ID(), mktInfo.Name) 442 continue 443 } 444 if quoteIsAcctBased && failedQuoteAccts[lo.QuoteAccount()] { 445 failedAcctOrders[lo.ID()] = struct{}{} 446 log.Warnf("Skipping insert of order %s into %s book because quote asset "+ 447 "account failed the balance check", lo.ID(), mktInfo.Name) 448 continue 449 } 450 if ok := Book.Insert(lo); !ok { 451 // This can only happen if one of the loaded orders has an 452 // incompatible lot size for the current market config, which was 453 // already checked above. 454 log.Errorf("Failed to insert order %v into %v book.", mktInfo.Name, lo) 455 } 456 } 457 458 // Revoke the low-balance rejections in the database. 459 for oid := range failedAcctOrders { 460 // Already logged in the Book.Insert loop. 461 if _, _, err = storage.RevokeOrderUncounted(bookOrdersByID[oid]); err != nil { 462 log.Errorf("Failed to revoke order with insufficient account balance %v: %v", bookOrdersByID[oid], err) 463 } 464 } 465 466 // Populate the order settling amount map from the active matches in DB. 467 activeMatches, err := storage.MarketMatches(base, quote) 468 if err != nil { 469 return nil, fmt.Errorf("failed to load active matches for market %v: %w", mktInfo.Name, err) 470 } 471 settling := make(map[order.OrderID]uint64) 472 for _, match := range activeMatches { 473 settling[match.Taker] += match.Quantity 474 settling[match.Maker] += match.Quantity 475 // Note: we actually don't want to bother with matches for orders that 476 // were canceled or had at-fault match failures, since including them 477 // give that user another shot to get a successfully "completed" order 478 // if they complete these remaining matches, but it's OK. We'd have to 479 // query these order statuses, and look for at-fault match failures 480 // involving them, so just give the user the benefit of the doubt. 481 } 482 log.Infof("Tracking %d orders with %d active matches.", len(settling), len(activeMatches)) 483 484 lastEpochEndRate, err := storage.LastEpochRate(base, quote) 485 if err != nil { 486 return nil, fmt.Errorf("failed to load last epoch end rate: %w", err) 487 } 488 489 return &Market{ 490 running: make(chan struct{}), // closed on market start 491 marketInfo: mktInfo, 492 book: Book, 493 settling: settling, 494 matcher: matcher.New(), 495 persistBook: true, 496 epochCommitments: make(map[order.Commitment]order.OrderID), 497 epochOrders: make(map[order.OrderID]order.Order), 498 swapper: swapper, 499 auth: cfg.AuthManager, 500 storage: storage, 501 coinLockerBase: cfg.CoinLockerBase, 502 coinLockerQuote: cfg.CoinLockerQuote, 503 baseFeeFetcher: cfg.FeeFetcherBase, 504 quoteFeeFetcher: cfg.FeeFetcherQuote, 505 dataCollector: cfg.DataCollector, 506 lastRate: lastEpochEndRate, 507 checkParcelLimit: cfg.CheckParcelLimit, 508 minimumRate: cfg.MinimumRate, 509 }, nil 510 } 511 512 // SuspendASAP suspends requests the market to gracefully suspend epoch cycling 513 // as soon as possible, always allowing an active epoch to close. See also 514 // Suspend. 515 func (m *Market) SuspendASAP(persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time) { 516 return m.Suspend(time.Now(), persistBook) 517 } 518 519 // Suspend requests the market to gracefully suspend epoch cycling as soon as 520 // the given time, always allowing the epoch including that time to complete. If 521 // the time is before the current epoch, the current epoch will be the last. 522 func (m *Market) Suspend(asSoonAs time.Time, persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time) { 523 // epochMtx guards activeEpochIdx, startEpochIdx, suspendEpochIdx, and 524 // persistBook. 525 m.epochMtx.Lock() 526 defer m.epochMtx.Unlock() 527 528 dur := int64(m.EpochDuration()) 529 530 epochEnd := func(idx int64) time.Time { 531 start := time.UnixMilli(idx * dur) 532 return start.Add(time.Duration(dur) * time.Millisecond) 533 } 534 535 // Determine which epoch includes asSoonAs, and compute its end time. If 536 // asSoonAs is in a past epoch, suspend at the end of the active epoch. 537 538 soonestFinalIdx := m.activeEpochIdx 539 if soonestFinalIdx == 0 { 540 // Cannot schedule a suspend if Run isn't running. 541 if m.startEpochIdx == 0 { 542 return -1, time.Time{} 543 } 544 // Not yet started. Soonest suspend idx is the start epoch idx - 1. 545 soonestFinalIdx = m.startEpochIdx - 1 546 } 547 548 if soonestEnd := epochEnd(soonestFinalIdx); asSoonAs.Before(soonestEnd) { 549 // Suspend at the end of the active epoch or the one prior to start. 550 finalEpochIdx = soonestFinalIdx 551 finalEpochEnd = soonestEnd 552 } else { 553 // Suspend at the end of the epoch that includes the target time. 554 ms := asSoonAs.UnixMilli() 555 finalEpochIdx = ms / dur 556 // Allow stopping at boundary, prior to the epoch starting at this time. 557 if ms%dur == 0 { 558 finalEpochIdx-- 559 } 560 finalEpochEnd = epochEnd(finalEpochIdx) 561 } 562 563 m.suspendEpochIdx = finalEpochIdx 564 m.persistBook = persistBook 565 566 return 567 } 568 569 // ResumeEpoch gets the next available resume epoch index for the currently 570 // configured epoch duration for the market and the provided earliest allowable 571 // start time. The market must be running, otherwise the zero index is returned. 572 func (m *Market) ResumeEpoch(asSoonAs time.Time) (startEpochIdx int64) { 573 // Only allow scheduling a resume if the market is not running. 574 if m.Running() { 575 return 576 } 577 578 dur := int64(m.EpochDuration()) 579 580 now := time.Now().UnixMilli() 581 nextEpochIdx := 1 + now/dur 582 583 ms := asSoonAs.UnixMilli() 584 startEpochIdx = 1 + ms/dur 585 586 if startEpochIdx < nextEpochIdx { 587 startEpochIdx = nextEpochIdx 588 } 589 return 590 } 591 592 // SetStartEpochIdx sets the starting epoch index. This should generally be 593 // called before Run, or Start used to specify the index at the same time. 594 func (m *Market) SetStartEpochIdx(startEpochIdx int64) { 595 m.epochMtx.Lock() 596 m.startEpochIdx = startEpochIdx 597 m.epochMtx.Unlock() 598 } 599 600 // Start begins order processing with a starting epoch index. See also 601 // SetStartEpochIdx and Run. Stop the Market by cancelling the context. 602 func (m *Market) Start(ctx context.Context, startEpochIdx int64) { 603 m.SetStartEpochIdx(startEpochIdx) 604 m.Run(ctx) 605 } 606 607 // waitForEpochOpen waits until the start of epoch processing. 608 func (m *Market) waitForEpochOpen() { 609 m.runMtx.RLock() 610 c := m.running // the field may be rewritten, but only after close 611 m.runMtx.RUnlock() 612 <-c 613 } 614 615 // Status describes the operation state of the Market. 616 type Status struct { 617 Running bool 618 EpochDuration uint64 // to compute times from epoch inds 619 ActiveEpoch int64 620 StartEpoch int64 621 SuspendEpoch int64 622 PersistBook bool 623 Base, Quote uint32 624 } 625 626 // Status returns the current operating state of the Market. 627 func (m *Market) Status() *Status { 628 m.epochMtx.Lock() 629 defer m.epochMtx.Unlock() 630 return &Status{ 631 Running: m.Running(), 632 EpochDuration: m.marketInfo.EpochDuration, 633 ActiveEpoch: m.activeEpochIdx, 634 StartEpoch: m.startEpochIdx, 635 SuspendEpoch: m.suspendEpochIdx, 636 PersistBook: m.persistBook, 637 Base: m.marketInfo.Base, 638 Quote: m.marketInfo.Quote, 639 } 640 } 641 642 // Running indicates is the market is accepting new orders. This will return 643 // false when suspended, but false does not necessarily mean Run has stopped 644 // since a start epoch may be set. Note that this method is of limited use and 645 // communicating subsystems shouldn't rely on the result for correct operation 646 // since a market could start or stop. Rather, they should infer or be informed 647 // of market status rather than rely on this. 648 // 649 // TODO: Instead of using Running in OrderRouter and DEX, these types should 650 // track statuses (known suspend times). 651 func (m *Market) Running() bool { 652 m.runMtx.RLock() 653 defer m.runMtx.RUnlock() 654 select { 655 case <-m.running: 656 return true 657 default: 658 return false 659 } 660 } 661 662 // EpochDuration returns the Market's epoch duration in milliseconds. 663 func (m *Market) EpochDuration() uint64 { 664 return m.marketInfo.EpochDuration 665 } 666 667 // MarketBuyBuffer returns the Market's market-buy buffer. 668 func (m *Market) MarketBuyBuffer() float64 { 669 return m.marketInfo.MarketBuyBuffer 670 } 671 672 // LotSize returns the market's lot size in units of the base asset. 673 func (m *Market) LotSize() uint64 { 674 return m.marketInfo.LotSize 675 } 676 677 // RateStep returns the market's rate step in units of the quote asset. 678 func (m *Market) RateStep() uint64 { 679 return m.marketInfo.RateStep 680 } 681 682 // Base is the base asset ID. 683 func (m *Market) Base() uint32 { 684 return m.marketInfo.Base 685 } 686 687 // Quote is the quote asset ID. 688 func (m *Market) Quote() uint32 { 689 return m.marketInfo.Quote 690 } 691 692 // OrderFeed provides a new order book update channel. Channels provided before 693 // the market starts and while a market is running are both valid. When the 694 // market stops, channels are closed (invalidated), and new channels should be 695 // requested if the market starts again. 696 func (m *Market) OrderFeed() <-chan *updateSignal { 697 bookUpdates := make(chan *updateSignal, 1) 698 m.orderFeedMtx.Lock() 699 m.orderFeeds = append(m.orderFeeds, bookUpdates) 700 m.orderFeedMtx.Unlock() 701 return bookUpdates 702 } 703 704 // FeedDone informs the market that the caller is finished receiving from the 705 // given channel, which should have been obtained from OrderFeed. If the channel 706 // was a registered order feed channel from OrderFeed, it is closed and removed 707 // so that no further signals will be send on the channel. 708 func (m *Market) FeedDone(feed <-chan *updateSignal) bool { 709 m.orderFeedMtx.Lock() 710 defer m.orderFeedMtx.Unlock() 711 for i := range m.orderFeeds { 712 if m.orderFeeds[i] == feed { 713 close(m.orderFeeds[i]) 714 // Order is not important to delete the channel without allocation. 715 m.orderFeeds[i] = m.orderFeeds[len(m.orderFeeds)-1] 716 m.orderFeeds[len(m.orderFeeds)-1] = nil // chan is a pointer 717 m.orderFeeds = m.orderFeeds[:len(m.orderFeeds)-1] 718 return true 719 } 720 } 721 return false 722 } 723 724 // sendToFeeds sends an *updateSignal to all order feed channels created with 725 // OrderFeed(). 726 func (m *Market) sendToFeeds(sig *updateSignal) { 727 m.orderFeedMtx.RLock() 728 for _, s := range m.orderFeeds { 729 s <- sig 730 } 731 m.orderFeedMtx.RUnlock() 732 } 733 734 type orderUpdateSignal struct { 735 rec *orderRecord 736 errChan chan error // should be buffered 737 } 738 739 func newOrderUpdateSignal(ord *orderRecord) *orderUpdateSignal { 740 return &orderUpdateSignal{ord, make(chan error, 1)} 741 } 742 743 // SubmitOrder submits a new order for inclusion into the current epoch. This is 744 // the synchronous version of SubmitOrderAsync. 745 func (m *Market) SubmitOrder(rec *orderRecord) error { 746 return <-m.SubmitOrderAsync(rec) 747 } 748 749 // processCancelOrderWhileSuspended is called when cancelling an order while 750 // the market is suspended and Run is not running. The error sent on errChan 751 // is returned to the client. 752 // 753 // This function: 754 // 1. Removes the target order from the book. 755 // 2. Unlocks the order coins. 756 // 3. Updates the storage with the new cancel order and cancels the existing limit order. 757 // 4. Responds to the client that the order was received. 758 // 5. Sends the unbooked order to the order feeds. 759 // 6. Creates a match object, stores it, and notifies the client of the match. 760 func (m *Market) processCancelOrderWhileSuspended(rec *orderRecord, errChan chan<- error) { 761 co, ok := rec.order.(*order.CancelOrder) 762 if !ok { 763 errChan <- ErrInvalidOrder 764 return 765 } 766 767 if cancelable, _, err := m.CancelableBy(co.TargetOrderID, co.AccountID); !cancelable { 768 errChan <- err 769 return 770 } 771 772 m.bookMtx.Lock() 773 delete(m.settling, co.TargetOrderID) 774 lo, ok := m.book.Remove(co.TargetOrderID) 775 m.bookMtx.Unlock() 776 if !ok { 777 errChan <- ErrTargetNotCancelable 778 return 779 } 780 781 m.unlockOrderCoins(lo) 782 783 sTime := time.Now().Truncate(time.Millisecond).UTC() 784 co.SetTime(sTime) 785 786 // Create the client response here, but don't send it until the order has been 787 // committed to the storage. 788 respMsg, err := m.orderResponse(rec) 789 if err != nil { 790 errChan <- fmt.Errorf("failed to create order response: %w", err) 791 return 792 } 793 794 dur := int64(m.EpochDuration()) 795 now := time.Now().UnixMilli() 796 epochIdx := now / dur 797 if err := m.storage.NewArchivedCancel(co, epochIdx, dur); err != nil { 798 errChan <- err 799 return 800 } 801 if err := m.storage.CancelOrder(lo); err != nil { 802 errChan <- err 803 return 804 } 805 806 err = m.auth.Send(rec.order.User(), respMsg) 807 if err != nil { 808 log.Errorf("Failed to send cancel order response: %v", err) 809 } 810 811 sig := &updateSignal{ 812 action: unbookAction, 813 data: sigDataUnbookedOrder{ 814 order: lo, 815 epochIdx: 0, 816 }, 817 } 818 m.sendToFeeds(sig) 819 820 match := order.Match{ 821 Taker: co, 822 Maker: lo, 823 Quantity: lo.Remaining(), 824 Rate: lo.Rate, 825 Epoch: order.EpochID{ 826 Idx: uint64(epochIdx), 827 Dur: m.EpochDuration(), 828 }, 829 FeeRateBase: m.getFeeRate(m.Base(), m.baseFeeFetcher), 830 FeeRateQuote: m.getFeeRate(m.Quote(), m.quoteFeeFetcher), 831 } 832 // insertMatchErr is sent on errChan at the end of the function. We 833 // want to send the match request to the client even if this insertion 834 // fails. 835 insertMatchErr := m.storage.InsertMatch(&match) 836 837 makerMsg, takerMsg := matchNotifications(&match) 838 m.auth.Sign(makerMsg) 839 m.auth.Sign(takerMsg) 840 msgs := []msgjson.Signable{makerMsg, takerMsg} 841 req, err := msgjson.NewRequest(comms.NextID(), msgjson.MatchRoute, msgs) 842 if err != nil { 843 log.Errorf("Failed to create match request: %v", err) 844 } else { 845 err = m.auth.Request(rec.order.User(), req, func(_ comms.Link, resp *msgjson.Message) { 846 m.processMatchAcksForCancel(rec.order.User(), resp) 847 }) 848 if err != nil { 849 log.Errorf("Failed to send match request: %v", err) 850 } 851 } 852 853 errChan <- insertMatchErr 854 } 855 856 // matchNotifications creates a pair of msgjson.Match from a match. 857 func matchNotifications(match *order.Match) (makerMsg *msgjson.Match, takerMsg *msgjson.Match) { 858 stamp := uint64(time.Now().UnixMilli()) 859 return &msgjson.Match{ 860 OrderID: idToBytes(match.Maker.ID()), 861 MatchID: idToBytes(match.ID()), 862 Quantity: match.Quantity, 863 Rate: match.Rate, 864 Address: order.ExtractAddress(match.Taker), 865 ServerTime: stamp, 866 FeeRateBase: match.FeeRateBase, 867 FeeRateQuote: match.FeeRateQuote, 868 Side: uint8(order.Maker), 869 }, &msgjson.Match{ 870 OrderID: idToBytes(match.Taker.ID()), 871 MatchID: idToBytes(match.ID()), 872 Quantity: match.Quantity, 873 Rate: match.Rate, 874 Address: order.ExtractAddress(match.Maker), 875 ServerTime: stamp, 876 FeeRateBase: match.FeeRateBase, 877 FeeRateQuote: match.FeeRateQuote, 878 Side: uint8(order.Taker), 879 } 880 } 881 882 // processMatchAcksForCancel is called when receiving a response to a match 883 // request for a cancel order. Nothing is done other than logging and verifying 884 // that the response is in the correct format. 885 // 886 // This is currently only used for cancel orders that happen while the market is 887 // suspended, but may be later used for all cancel orders. 888 func (m *Market) processMatchAcksForCancel(user account.AccountID, msg *msgjson.Message) { 889 var acks []msgjson.Acknowledgement 890 err := msg.UnmarshalResult(&acks) 891 if err != nil { 892 m.respondError(msg.ID, user, msgjson.RPCParseError, 893 fmt.Sprintf("error parsing match request acknowledgment: %v", err)) 894 return 895 } 896 // The acknowledgment for both the taker and maker should come from the same user 897 expectedNumAcks := 2 898 if len(acks) != expectedNumAcks { 899 m.respondError(msg.ID, user, msgjson.AckCountError, 900 fmt.Sprintf("expected %d acknowledgements, got %d", expectedNumAcks, len(acks))) 901 return 902 } 903 log.Debugf("processMatchAcksForCancel: 'match' ack received from %v", user) 904 } 905 906 // SubmitOrderAsync submits a new order for inclusion into the current epoch. 907 // When submission is completed, an error value will be sent on the channel. 908 // This is the asynchronous version of SubmitOrder. 909 func (m *Market) SubmitOrderAsync(rec *orderRecord) <-chan error { 910 sendErr := func(err error) <-chan error { 911 errChan := make(chan error, 1) 912 errChan <- err // i.e. ErrInvalidOrder, ErrInvalidCommitment 913 return errChan 914 } 915 916 // Validate the order. The order router must do it's own validation, but do 917 // a second validation for (1) this Market and (2) epoch status, before 918 // putting it on the queue. 919 if err := m.validateOrder(rec.order); err != nil { 920 // Order ID cannot be computed since ServerTime has not been set. 921 log.Debugf("SubmitOrderAsync: Invalid order received from user %v with commitment %v: %v", 922 rec.order.User(), rec.order.Commitment(), err) 923 return sendErr(err) 924 } 925 926 // Only submit orders while market is running. 927 m.runMtx.RLock() 928 defer m.runMtx.RUnlock() 929 930 select { 931 case <-m.running: 932 default: 933 if rec.order.Type() == order.CancelOrderType { 934 errChan := make(chan error, 1) 935 go m.processCancelOrderWhileSuspended(rec, errChan) 936 return errChan 937 } 938 // m.orderRouter is closed 939 log.Infof("SubmitOrderAsync: Market stopped with an order in submission (commitment %v).", 940 rec.order.Commitment()) // The order is not time stamped, so no OrderID. 941 return sendErr(ErrMarketNotRunning) 942 } 943 944 sig := newOrderUpdateSignal(rec) 945 // The lock is still held, so there is a receiver: either Run's main loop or 946 // the drain in Run's defer that runs until m.running starts blocking. 947 m.orderRouter <- sig 948 return sig.errChan 949 } 950 951 // MidGap returns the mid-gap market rate, which is ths rate halfway between the 952 // best buy order and the best sell order in the order book. If one side has no 953 // orders, the best order rate on other side is returned. If both sides have no 954 // orders, 0 is returned. 955 func (m *Market) MidGap() uint64 { 956 _, mid, _ := m.rates() 957 return mid 958 } 959 960 func (m *Market) rates() (bestBuyRate, mid, bestSellRate uint64) { 961 bestBuy, bestSell := m.book.Best() 962 if bestBuy == nil { 963 if bestSell == nil { 964 return 965 } 966 return 0, bestSell.Rate, bestSell.Rate 967 } else if bestSell == nil { 968 return bestBuy.Rate, bestBuy.Rate, math.MaxUint64 969 } 970 mid = (bestBuy.Rate + bestSell.Rate) / 2 // note downward bias on truncate 971 return bestBuy.Rate, mid, bestSell.Rate 972 } 973 974 // CoinLocked checks if a coin is locked. The asset is specified since we should 975 // not assume that a CoinID for one asset cannot be made to match another 976 // asset's CoinID. 977 func (m *Market) CoinLocked(asset uint32, coin coinlock.CoinID) bool { 978 switch { 979 case asset == m.marketInfo.Base && m.coinLockerBase != nil: 980 return m.coinLockerBase.CoinLocked(coin) 981 case asset == m.marketInfo.Quote && m.coinLockerQuote != nil: 982 return m.coinLockerQuote.CoinLocked(coin) 983 default: 984 panic(fmt.Sprintf("invalid utxo-based asset %d for market %s", asset, m.marketInfo.Name)) 985 } 986 } 987 988 // Cancelable determines if an order is a limit order with time-in-force 989 // standing that is in either the epoch queue or in the order book. 990 func (m *Market) Cancelable(oid order.OrderID) bool { 991 // All book orders are standing limit orders. 992 if m.book.HaveOrder(oid) { 993 return true 994 } 995 996 // Check the active epochs (includes current and next). 997 m.epochMtx.RLock() 998 ord := m.epochOrders[oid] 999 m.epochMtx.RUnlock() 1000 1001 if lo, ok := ord.(*order.LimitOrder); ok { 1002 return lo.Force == order.StandingTiF 1003 } 1004 return false 1005 } 1006 1007 // CancelableBy determines if an order is cancelable by a certain account. This 1008 // means: (1) an order in the book or epoch queue, (2) type limit with 1009 // time-in-force standing (implied for book orders), and (3) AccountID field 1010 // matching the provided account ID. 1011 func (m *Market) CancelableBy(oid order.OrderID, aid account.AccountID) (bool, time.Time, error) { 1012 // All book orders are standing limit orders. 1013 if lo := m.book.Order(oid); lo != nil { 1014 if lo.AccountID == aid { 1015 return true, lo.ServerTime, nil 1016 } 1017 return false, time.Time{}, ErrCancelNotPermitted 1018 } 1019 1020 // Check the active epochs (includes current and next). 1021 m.epochMtx.RLock() 1022 ord := m.epochOrders[oid] 1023 m.epochMtx.RUnlock() 1024 1025 if ord == nil { 1026 return false, time.Time{}, ErrTargetNotActive 1027 } 1028 1029 lo, ok := ord.(*order.LimitOrder) 1030 if !ok { 1031 return false, time.Time{}, ErrTargetNotCancelable 1032 } 1033 if lo.Force != order.StandingTiF { 1034 return false, time.Time{}, ErrTargetNotCancelable 1035 } 1036 if lo.AccountID != aid { 1037 return false, time.Time{}, ErrCancelNotPermitted 1038 } 1039 return true, lo.ServerTime, nil 1040 } 1041 1042 func (m *Market) checkUnfilledOrders(assetID uint32, unfilled []*order.LimitOrder) (unbooked []*order.LimitOrder) { 1043 checkUnspent := func(assetID uint32, coinID []byte) error { 1044 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 1045 defer cancel() 1046 return m.swapper.CheckUnspent(ctx, assetID, coinID) 1047 } 1048 1049 orders: 1050 for _, lo := range unfilled { 1051 log.Tracef("Checking %d funding coins for order %v", len(lo.Coins), lo.ID()) 1052 for i := range lo.Coins { 1053 err := checkUnspent(assetID, lo.Coins[i]) 1054 if err == nil { 1055 continue // unspent, check next coin 1056 } 1057 1058 if !errors.Is(err, asset.CoinNotFoundError) { 1059 // other failure (timeout, coinID decode, RPC, etc.) 1060 log.Errorf("Unexpected error checking coinID %v for order %v: %v", 1061 lo.Coins[i], lo, err) 1062 continue orders 1063 // NOTE: This does not revoke orders from storage since this is 1064 // likely to be a configuration or node issue. 1065 } 1066 1067 // Final fill amount check in case it was matched after we pulled 1068 // the list of unfilled orders from the book. 1069 if lo.Filled() == 0 { 1070 log.Warnf("Coin %s not unspent for unfilled order %v. "+ 1071 "Revoking the order.", fmtCoinID(assetID, lo.Coins[i]), lo) 1072 m.Unbook(lo) 1073 unbooked = append(unbooked, lo) 1074 } 1075 continue orders 1076 } 1077 } 1078 return 1079 } 1080 1081 // SwapDone registers a match for a given order as being finished. Whether the 1082 // match was a successful or failed swap is indicated by fail. This is used to 1083 // (1) register completed orders for cancellation rate purposes, and (2) to 1084 // unbook at-fault limit orders. 1085 // 1086 // Implementation note: Orders that have failed a swap or were canceled (see 1087 // processReadyEpoch) are removed from the settling map regardless of any amount 1088 // still setting for such orders. 1089 func (m *Market) SwapDone(ord order.Order, match *order.Match, fail bool) { 1090 oid := ord.ID() 1091 m.bookMtx.Lock() 1092 defer m.bookMtx.Unlock() 1093 settling, found := m.settling[oid] 1094 if !found { 1095 // Order was canceled, revoked, or already had failed swap, and was 1096 // removed from the map. No more settling amount tracking needed. 1097 return 1098 } 1099 if settling < match.Quantity { 1100 log.Errorf("Finished swap %v (qty %d) for order %v larger than current settling (%d) amount.", 1101 match.ID(), match.Quantity, oid, settling) 1102 settling = 0 1103 } else { 1104 settling -= match.Quantity 1105 } 1106 1107 // Limit orders may need to be unbooked, or considered for further matches. 1108 lo, limit := ord.(*order.LimitOrder) 1109 1110 // For a failed swap, remove the map entry, and unbook/revoke the order. 1111 if fail { 1112 delete(m.settling, oid) 1113 if limit { 1114 // Try to unbook and revoke failed limit orders. 1115 _, removed := m.book.Remove(oid) 1116 m.unlockOrderCoins(lo) 1117 if removed { 1118 // Lazily update DB and auth, and notify orderbook subscribers. 1119 m.lazy(func() { m.unbookedOrder(lo) }) 1120 } 1121 } 1122 return 1123 } 1124 1125 // Continue tracking if there are swaps settling or it is booked (more 1126 // matches can be made). We check Book.HaveOrder instead of Remaining since 1127 // the provided Order instance may not belong to Market and may thus be out 1128 // of sync with respect to filled amount. 1129 if settling > 0 || (limit && lo.Force == order.StandingTiF && m.book.HaveOrder(oid)) { 1130 m.settling[oid] = settling 1131 return 1132 } 1133 1134 // The order can no longer be matched and nothing is settling. 1135 delete(m.settling, oid) 1136 1137 // Register the order as successfully completed in the auth manager. 1138 compTime := time.Now().UTC() 1139 m.auth.RecordCompletedOrder(ord.User(), oid, compTime) 1140 // Record the successful completion time. 1141 if err := m.storage.SetOrderCompleteTime(ord, compTime.UnixMilli()); err != nil { 1142 if db.IsErrGeneralFailure(err) { 1143 log.Errorf("fatal error with SetOrderCompleteTime for order %v: %v", ord, err) 1144 return 1145 } 1146 log.Errorf("SetOrderCompleteTime for %v: %v", ord, err) 1147 } 1148 } 1149 1150 // CheckUnfilled checks unfilled book orders belonging to a user and funded by 1151 // coins for a given asset to ensure that their funding coins are not spent. If 1152 // any of an order's funding coins are spent, the order is unbooked (removed 1153 // from the in-memory book, revoked in the DB, a cancellation marked against the 1154 // user, coins unlocked, and orderbook subscribers notified). See Unbook for 1155 // details. 1156 func (m *Market) CheckUnfilled(assetID uint32, user account.AccountID) (unbooked []*order.LimitOrder) { 1157 base, quote := m.marketInfo.Base, m.marketInfo.Quote 1158 if assetID != base && assetID != quote { 1159 return 1160 } 1161 var unfilled []*order.LimitOrder 1162 switch assetID { 1163 case base: 1164 // Sell orders are funded by the base asset. 1165 unfilled = m.book.UnfilledUserSells(user) 1166 case quote: 1167 // Buy orders are funded by the quote asset. 1168 unfilled = m.book.UnfilledUserBuys(user) 1169 default: 1170 return 1171 } 1172 1173 return m.checkUnfilledOrders(assetID, unfilled) 1174 } 1175 1176 // AccountPending sums the orders quantities that pay to or from the specified 1177 // account address. 1178 func (m *Market) AccountPending(acctAddr string, assetID uint32) (qty, lots uint64, redeems int) { 1179 base, quote := m.marketInfo.Base, m.marketInfo.Quote 1180 if (assetID != base && assetID != quote) || 1181 (assetID == m.marketInfo.Base && m.coinLockerBase != nil) || 1182 (assetID == m.marketInfo.Quote && m.coinLockerQuote != nil) { 1183 1184 return 1185 } 1186 1187 midGap := m.MidGap() 1188 if midGap == 0 { 1189 midGap = m.RateStep() 1190 } 1191 1192 lotSize := m.marketInfo.LotSize 1193 switch assetID { 1194 case base: 1195 m.iterateBaseAccount(acctAddr, func(trade *order.Trade, rate uint64) { 1196 r := trade.Remaining() 1197 if trade.Sell { 1198 qty += r 1199 lots += r / lotSize 1200 } else { 1201 if rate == 0 { // market buy 1202 redeems += int(calc.QuoteToBase(midGap, r) / lotSize) 1203 } else { 1204 redeems += int(r / lotSize) 1205 } 1206 } 1207 }) 1208 case quote: 1209 m.iterateQuoteAccount(acctAddr, func(trade *order.Trade, rate uint64) { 1210 r := trade.Remaining() 1211 if trade.Sell { 1212 redeems += int(r / lotSize) 1213 } else { 1214 if rate == 0 { // market buy 1215 qty += r 1216 lots += calc.QuoteToBase(midGap, r) / lotSize 1217 } else { 1218 qty += calc.BaseToQuote(midGap, r) 1219 lots += r / lotSize 1220 } 1221 } 1222 }) 1223 } 1224 return 1225 } 1226 1227 func (m *Market) iterateBaseAccount(acctAddr string, f func(*order.Trade, uint64)) { 1228 m.epochMtx.RLock() 1229 for _, epOrd := range m.epochOrders { 1230 if epOrd.Type() == order.CancelOrderType || epOrd.Trade().BaseAccount() != acctAddr { 1231 continue 1232 } 1233 var rate uint64 1234 if lo, is := epOrd.(*order.LimitOrder); is { 1235 rate = lo.Rate 1236 } 1237 f(epOrd.Trade(), rate) 1238 1239 } 1240 m.epochMtx.RUnlock() 1241 m.book.IterateBaseAccount(acctAddr, func(lo *order.LimitOrder) { 1242 f(lo.Trade(), lo.Rate) 1243 }) 1244 } 1245 1246 func (m *Market) iterateQuoteAccount(acctAddr string, f func(*order.Trade, uint64)) { 1247 m.epochMtx.RLock() 1248 for _, epOrd := range m.epochOrders { 1249 if epOrd.Type() == order.CancelOrderType || epOrd.Trade().QuoteAccount() != acctAddr { 1250 continue 1251 } 1252 var rate uint64 1253 if lo, is := epOrd.(*order.LimitOrder); is { 1254 rate = lo.Rate 1255 } 1256 f(epOrd.Trade(), rate) 1257 } 1258 m.epochMtx.RUnlock() 1259 m.book.IterateQuoteAccount(acctAddr, func(lo *order.LimitOrder) { 1260 f(lo.Trade(), lo.Rate) 1261 }) 1262 } 1263 1264 // Book retrieves the market's current order book and the current epoch index. 1265 // If the Market is not yet running or the start epoch has not yet begun, the 1266 // epoch index will be zero. 1267 func (m *Market) Book() (epoch int64, buys, sells []*order.LimitOrder) { 1268 // NOTE: it may be desirable to cache the response. 1269 m.bookMtx.Lock() 1270 buys = m.book.BuyOrders() 1271 sells = m.book.SellOrders() 1272 epoch = m.bookEpochIdx 1273 m.bookMtx.Unlock() 1274 return 1275 } 1276 1277 // PurgeBook flushes all booked orders from the in-memory book and persistent 1278 // storage. In terms of storage, this means changing orders with status booked 1279 // to status revoked. 1280 func (m *Market) PurgeBook() { 1281 // Clear booked orders from the DB and the in-memory book. 1282 removed := m.purgeBook() 1283 1284 // Send individual revoke order notifications. These are not part of the 1285 // orderbook subscription, so the users will receive them whether or not 1286 // they are subscribed for book updates. 1287 for oid, aid := range removed { 1288 m.sendRevokeOrderNote(oid, aid) 1289 } 1290 } 1291 1292 func (m *Market) purgeBook() (removed map[order.OrderID]account.AccountID) { 1293 m.bookMtx.Lock() 1294 defer m.bookMtx.Unlock() 1295 1296 // Revoke all booked orders in the DB. 1297 sellsCleared, buysCleared, err := m.storage.FlushBook(m.marketInfo.Base, m.marketInfo.Quote) 1298 if err != nil { 1299 log.Errorf("Failed to flush book for market %s: %v", m.marketInfo.Name, err) 1300 return 1301 } 1302 1303 // Clear the in-memory order book to match the DB. 1304 buysRemoved, sellsRemoved := m.book.Clear() 1305 1306 log.Infof("Flushed %d sell orders and %d buy orders from market %q book", 1307 len(sellsRemoved), len(buysRemoved), m.marketInfo.Name) 1308 // Maybe the DB cleaned up orphaned orders. Log any discrepancies. 1309 if len(sellsRemoved) != len(sellsCleared) { 1310 log.Warnf("Removed %d sell orders from the book, but %d were updated in the DB.", 1311 len(sellsRemoved), len(sellsCleared)) 1312 } 1313 if len(buysRemoved) != len(buysCleared) { 1314 log.Warnf("Removed %d buy orders from the book, but %d were updated in the DB.", 1315 len(buysRemoved), len(buysCleared)) 1316 } 1317 1318 // Unlock coins for removed orders. 1319 1320 // TODO: only unlock previously booked order coins, do not include coins 1321 // that might belong to orders still in epoch status. This won't matter if 1322 // the market is suspended, but it does if PurgeBook is used while the 1323 // market is still accepting new orders and processing epochs. 1324 1325 // Unlock base asset coins locked by sell orders. 1326 if m.coinLockerBase != nil { 1327 for i := range sellsRemoved { 1328 m.coinLockerBase.UnlockOrderCoins(sellsRemoved[i].ID()) 1329 } 1330 } 1331 1332 // Unlock quote asset coins locked by buy orders. 1333 if m.coinLockerQuote != nil { 1334 for i := range buysRemoved { 1335 m.coinLockerQuote.UnlockOrderCoins(buysRemoved[i].ID()) 1336 } 1337 } 1338 1339 removed = make(map[order.OrderID]account.AccountID, len(buysRemoved)+len(sellsRemoved)) 1340 for _, lo := range append(sellsRemoved, buysRemoved...) { 1341 removed[lo.ID()] = lo.AccountID 1342 } 1343 1344 return 1345 } 1346 1347 func (m *Market) lazy(do func()) { 1348 m.tasks.Add(1) 1349 go func() { 1350 defer m.tasks.Done() 1351 do() 1352 }() 1353 } 1354 1355 // Run is the main order processing loop, which takes new orders, notifies book 1356 // subscribers, and cycles the epochs. The caller should cancel the provided 1357 // Context to stop the market. The outgoing order feed channels persist after 1358 // Run returns for possible Market resume, and for Swapper's unbook callback to 1359 // function using sendToFeeds. 1360 func (m *Market) Run(ctx context.Context) { 1361 // Prevent multiple incantations of Run. 1362 if !atomic.CompareAndSwapUint32(&m.up, 0, 1) { 1363 log.Errorf("Run: Market not stopped!") 1364 return 1365 } 1366 defer atomic.StoreUint32(&m.up, 0) 1367 1368 var running bool 1369 ctxRun, cancel := context.WithCancel(ctx) 1370 var wgFeeds, wgEpochs sync.WaitGroup 1371 notifyChan := make(chan *updateSignal, 32) 1372 1373 // For clarity, define the shutdown sequence in a single closure rather than 1374 // the defer stack. 1375 defer func() { 1376 // Drain the order router of incoming orders that made it in after the 1377 // main loop broke and before flagging the market stopped. Do this in a 1378 // goroutine because the market is flagged as stopped under runMtx lock 1379 // in this defer and there is a risk of deadlock in SubmitOrderAsync 1380 // that sends under runMtx lock as well. 1381 wgFeeds.Add(1) 1382 go func() { 1383 defer wgFeeds.Done() 1384 for sig := range m.orderRouter { 1385 sig.errChan <- ErrMarketNotRunning 1386 } 1387 }() 1388 1389 // Under lock, flag as not running. 1390 m.runMtx.Lock() // block while SubmitOrderAsync is sending to the drain 1391 if !running { 1392 // In case the market is stopped before the first epoch, close the 1393 // running channel so that waitForEpochOpen does not hang. 1394 close(m.running) 1395 } 1396 m.running = make(chan struct{}) 1397 running = false 1398 close(m.orderRouter) // stop the order router drain 1399 m.runMtx.Unlock() 1400 1401 // Stop and wait for epoch pump and processing pipeline goroutines. 1402 cancel() // may already be done by suspend 1403 wgEpochs.Wait() 1404 // Book mod goroutines done, may purge if requested. 1405 1406 // persistBook is set under epochMtx lock. 1407 m.epochMtx.Lock() 1408 1409 // Signal to the book router of the suspend now that the closed epoch 1410 // processing pipeline is finished (wgEpochs). 1411 notifyChan <- &updateSignal{ 1412 action: suspendAction, 1413 data: sigDataSuspend{ 1414 finalEpoch: m.activeEpochIdx, 1415 persistBook: m.persistBook, 1416 }, 1417 } 1418 1419 if !m.persistBook { 1420 m.PurgeBook() 1421 } 1422 1423 m.persistBook = true // future resume default 1424 m.activeEpochIdx = 0 1425 1426 // Revoke any unmatched epoch orders (if context was canceled, not a 1427 // clean suspend stopped the market). 1428 for oid, ord := range m.epochOrders { 1429 log.Infof("Dropping epoch order %v", oid) 1430 if co, ok := ord.(*order.CancelOrder); ok { 1431 if err := m.storage.FailCancelOrder(co); err != nil { 1432 log.Errorf("Failed to set orphaned epoch cancel order %v as executed: %v", oid, err) 1433 } 1434 continue 1435 } 1436 if err := m.storage.ExecuteOrder(ord); err != nil { 1437 log.Errorf("Failed to set orphaned epoch trade order %v as executed: %v", oid, err) 1438 } 1439 } 1440 m.epochMtx.Unlock() 1441 1442 // Stop and wait for the order feed goroutine. 1443 close(notifyChan) 1444 wgFeeds.Wait() 1445 1446 m.tasks.Wait() 1447 1448 log.Infof("Market %q stopped.", m.marketInfo.Name) 1449 }() 1450 1451 // Start outgoing order feed notification goroutine. 1452 wgFeeds.Add(1) 1453 go func() { 1454 defer wgFeeds.Done() 1455 for sig := range notifyChan { 1456 m.sendToFeeds(sig) 1457 } 1458 }() 1459 1460 // Start the closed epoch pump, which drives preimage collection and orderly 1461 // epoch processing. 1462 eq := newEpochPump() 1463 wgEpochs.Add(1) 1464 go func() { 1465 defer wgEpochs.Done() 1466 eq.Run(ctxRun) 1467 }() 1468 1469 // Start the closed epoch processing pipeline. 1470 wgEpochs.Add(1) 1471 go func() { 1472 defer wgEpochs.Done() 1473 for ep := range eq.ready { 1474 // prepEpoch has completed preimage collection. 1475 m.processReadyEpoch(ep, notifyChan) 1476 } 1477 log.Debugf("epoch pump drained for market %s", m.marketInfo.Name) 1478 // There must be no more notify calls. 1479 }() 1480 1481 m.epochMtx.Lock() 1482 nextEpochIdx := m.startEpochIdx 1483 if nextEpochIdx == 0 { 1484 log.Warnf("Run: startEpochIdx not set. Starting at the next epoch.") 1485 now := time.Now().UnixMilli() 1486 nextEpochIdx = 1 + now/int64(m.EpochDuration()) 1487 m.startEpochIdx = nextEpochIdx 1488 } 1489 m.epochMtx.Unlock() 1490 1491 epochDuration := int64(m.marketInfo.EpochDuration) 1492 nextEpoch := NewEpoch(nextEpochIdx, epochDuration) 1493 epochCycle := time.After(time.Until(nextEpoch.Start)) 1494 1495 var currentEpoch *EpochQueue 1496 cycleEpoch := func() { 1497 if currentEpoch != nil { 1498 // Process the epoch asynchronously since there is a delay while the 1499 // preimages are requested and clients respond with their preimages. 1500 if !m.enqueueEpoch(eq, currentEpoch) { 1501 return 1502 } 1503 1504 // The epoch is closed, long live the epoch. 1505 sig := &updateSignal{ 1506 action: newEpochAction, 1507 data: sigDataNewEpoch{idx: nextEpoch.Epoch}, 1508 } 1509 notifyChan <- sig 1510 } 1511 1512 // Guard activeEpochIdx and suspendEpochIdx. 1513 m.epochMtx.Lock() 1514 defer m.epochMtx.Unlock() 1515 1516 // Check suspendEpochIdx and suspend if the just-closed epoch idx is the 1517 // suspend epoch. 1518 if m.suspendEpochIdx == nextEpoch.Epoch-1 { 1519 // Reject incoming orders. 1520 currentEpoch = nil 1521 cancel() // graceful market shutdown 1522 return 1523 } 1524 1525 currentEpoch = nextEpoch 1526 nextEpochIdx = currentEpoch.Epoch + 1 1527 m.activeEpochIdx = currentEpoch.Epoch 1528 1529 if !running { 1530 // Check that both blockchains are synced before actually starting. 1531 synced, err := m.swapper.ChainsSynced(m.marketInfo.Base, m.marketInfo.Quote) 1532 if err != nil { 1533 log.Errorf("Not starting %s market because of ChainsSynced error: %v", m.marketInfo.Name, err) 1534 } else if !synced { 1535 log.Debugf("Delaying start of %s market because chains aren't synced", m.marketInfo.Name) 1536 } else { 1537 // Open up SubmitOrderAsync. 1538 close(m.running) 1539 running = true 1540 log.Infof("Market %s now accepting orders, epoch %d:%d", m.marketInfo.Name, 1541 currentEpoch.Epoch, epochDuration) 1542 // Signal to the book router if this is a resume. 1543 if m.suspendEpochIdx != 0 { 1544 notifyChan <- &updateSignal{ 1545 action: resumeAction, 1546 data: sigDataResume{ 1547 epochIdx: currentEpoch.Epoch, 1548 // TODO: signal config or new config 1549 }, 1550 } 1551 } 1552 } 1553 } 1554 1555 // Replace the next epoch and set the cycle Timer. 1556 nextEpoch = NewEpoch(nextEpochIdx, epochDuration) 1557 epochCycle = time.After(time.Until(nextEpoch.Start)) 1558 } 1559 1560 // Set the orderRouter field now since the main loop below receives on it, 1561 // even though SubmitOrderAsync disallows sends on orderRouter when the 1562 // market is not running. 1563 m.orderRouter = make(chan *orderUpdateSignal, 32) // implicitly guarded by m.runMtx since Market is not running yet 1564 1565 for { 1566 if ctxRun.Err() != nil { 1567 return 1568 } 1569 1570 if err := m.storage.LastErr(); err != nil { 1571 log.Criticalf("Archivist failing. Last unexpected error: %v", err) 1572 return 1573 } 1574 1575 // Prioritize the epoch cycle. 1576 select { 1577 case <-epochCycle: 1578 cycleEpoch() 1579 default: 1580 } 1581 1582 // cycleEpoch can cancel ctxRun if suspend initiated. 1583 if ctxRun.Err() != nil { 1584 return 1585 } 1586 1587 // Wait for the next signal (cancel, new order, or epoch cycle). 1588 select { 1589 case <-ctxRun.Done(): 1590 return 1591 1592 case s := <-m.orderRouter: 1593 if currentEpoch == nil { 1594 // The order is not time-stamped yet, so the ID cannot be computed. 1595 log.Debugf("Order type %v received prior to market start.", s.rec.order.Type()) 1596 s.errChan <- ErrMarketNotRunning 1597 continue 1598 } 1599 1600 // Set the order's server time stamp, giving the order a valid ID. 1601 sTime := time.Now().Truncate(time.Millisecond).UTC() 1602 s.rec.order.SetTime(sTime) // Order.ID()/UID()/String() is OK now. 1603 log.Tracef("Received order %v at %v", s.rec.order, sTime) 1604 1605 // Push the order into the next epoch if receiving and stamping it 1606 // took just a little too long. 1607 var orderEpoch *EpochQueue 1608 switch { 1609 case currentEpoch.IncludesTime(sTime): 1610 orderEpoch = currentEpoch 1611 case nextEpoch.IncludesTime(sTime): 1612 log.Infof("Order %v (sTime=%d) fell into the next epoch [%d,%d)", 1613 s.rec.order, sTime.UnixNano(), nextEpoch.Start.Unix(), nextEpoch.End.Unix()) 1614 orderEpoch = nextEpoch 1615 default: 1616 // This should not happen. 1617 log.Errorf("Time %d does not fit into current or next epoch!", 1618 sTime.UnixNano()) 1619 s.errChan <- ErrEpochMissed 1620 continue 1621 } 1622 1623 // Process the order in the target epoch queue. 1624 err := m.processOrder(s.rec, orderEpoch, notifyChan, s.errChan) 1625 if err != nil { 1626 log.Errorf("Failed to process order %v: %v", s.rec.order, err) 1627 // Signal to the other Run goroutines to return. 1628 return 1629 } 1630 1631 case <-epochCycle: 1632 cycleEpoch() 1633 } 1634 } 1635 1636 } 1637 1638 func (m *Market) coinsLocked(o order.Order) ([]order.CoinID, uint32) { 1639 if o.Type() == order.CancelOrderType { 1640 return nil, 0 1641 } 1642 1643 locker := m.coinLockerQuote 1644 assetID := m.marketInfo.Quote 1645 if o.Trade().Trade().Sell { 1646 locker = m.coinLockerBase 1647 assetID = m.marketInfo.Base 1648 } 1649 1650 if locker == nil { // Not utxo-based 1651 return nil, 0 1652 } 1653 1654 // Check if this order is known by the locker. 1655 lockedCoins := locker.OrderCoinsLocked(o.ID()) 1656 if len(lockedCoins) > 0 { 1657 return lockedCoins, assetID 1658 } 1659 1660 // Check the individual coins. 1661 for _, coin := range o.Trade().Coins { 1662 if locker.CoinLocked(coin) { 1663 lockedCoins = append(lockedCoins, coin) 1664 } 1665 } 1666 return lockedCoins, assetID 1667 } 1668 1669 func (m *Market) lockOrderCoins(o order.Order) { 1670 if o.Type() == order.CancelOrderType { 1671 return 1672 } 1673 1674 if o.Trade().Sell { 1675 if m.coinLockerBase != nil { 1676 m.coinLockerBase.LockOrdersCoins([]order.Order{o}) 1677 } 1678 1679 } else if m.coinLockerQuote != nil { 1680 m.coinLockerQuote.LockOrdersCoins([]order.Order{o}) 1681 } 1682 } 1683 1684 func (m *Market) unlockOrderCoins(o order.Order) { 1685 if o.Type() == order.CancelOrderType { 1686 return 1687 } 1688 1689 if o.Trade().Sell { 1690 if m.coinLockerBase != nil { 1691 m.coinLockerBase.UnlockOrderCoins(o.ID()) 1692 } 1693 } else if m.coinLockerQuote != nil { 1694 m.coinLockerQuote.UnlockOrderCoins(o.ID()) 1695 } 1696 } 1697 1698 func (m *Market) analysisHelpers() ( 1699 likelyTaker func(ord order.Order) bool, 1700 baseQty func(ord order.Order) uint64, 1701 ) { 1702 bestBuy, midGap, bestSell := m.rates() 1703 likelyTaker = func(ord order.Order) bool { 1704 lo, ok := ord.(*order.LimitOrder) 1705 if !ok || lo.Force == order.ImmediateTiF { 1706 return true 1707 } 1708 // Must cross the spread to be a taker (not so conservative). 1709 switch { 1710 case midGap == 0: 1711 return false // empty market: could be taker, but assume not 1712 case lo.Sell: 1713 return lo.Rate <= bestBuy 1714 default: 1715 return lo.Rate >= bestSell 1716 } 1717 } 1718 baseQty = func(ord order.Order) uint64 { 1719 if ord.Type() == order.CancelOrderType { 1720 return 0 1721 } 1722 qty := ord.Trade().Quantity 1723 if ord.Type() == order.MarketOrderType && !ord.Trade().Sell { 1724 // Market buy qty is in quote asset. Convert to base. 1725 if midGap == 0 { 1726 qty = m.marketInfo.LotSize // no orders on the book; call it 1 lot 1727 } else { 1728 qty = calc.QuoteToBase(midGap, qty) 1729 } 1730 } 1731 return qty 1732 } 1733 return 1734 } 1735 1736 // ParcelSize returns market's configured parcel size. 1737 func (m *Market) ParcelSize() uint32 { 1738 return m.marketInfo.ParcelSize 1739 } 1740 1741 // Parcels calculates the total parcels for the market with the specified 1742 // settling quantity. Parcels is used as part of order validation for global 1743 // parcel limits. Parcels is not called for the market for which the order is 1744 // for, which will use m.checkParcelLimit to validate in processOrder. 1745 func (m *Market) Parcels(user account.AccountID, settlingQty uint64) float64 { 1746 return m.parcels(user, settlingQty) 1747 } 1748 1749 func (m *Market) parcels(user account.AccountID, addParcelWeight uint64) float64 { 1750 likelyTaker, baseQty := m.analysisHelpers() 1751 var takerQty, makerQty uint64 1752 m.epochMtx.RLock() 1753 for _, epOrd := range m.epochOrders { 1754 if epOrd.User() != user || epOrd.Type() == order.CancelOrderType { 1755 continue 1756 } 1757 1758 // Even if standing, may count as taker for purposes of taker qty limit. 1759 if likelyTaker(epOrd) { 1760 takerQty += baseQty(epOrd) 1761 } else { 1762 makerQty += baseQty(epOrd) 1763 } 1764 } 1765 m.epochMtx.RUnlock() 1766 1767 bookedBuyAmt, bookedSellAmt, _, _ := m.book.UserOrderTotals(user) 1768 makerQty += bookedBuyAmt + bookedSellAmt 1769 return calc.Parcels(makerQty+addParcelWeight, takerQty, m.marketInfo.LotSize, m.marketInfo.ParcelSize) 1770 } 1771 1772 // processOrder performs the following actions: 1773 // 1. Verify the order is new and that none of the backing coins are locked. 1774 // 2. Lock the order's coins. 1775 // 3. Store the order in the DB. 1776 // 4. Insert the order into the EpochQueue. 1777 // 5. Respond to the client that placed the order. 1778 // 6. Notify epoch queue event subscribers. 1779 func (m *Market) processOrder(rec *orderRecord, epoch *EpochQueue, notifyChan chan<- *updateSignal, errChan chan<- error) error { 1780 // Disallow trade orders from suspended accounts. Cancel orders are allowed. 1781 if rec.order.Type() != order.CancelOrderType { 1782 // Do not bother the auth manager for cancel orders. 1783 if _, tier := m.auth.AcctStatus(rec.order.User()); tier < 1 { 1784 log.Debugf("Account %v with tier %d not allowed to submit order %v", rec.order.User(), tier, rec.order.ID()) 1785 errChan <- ErrSuspendedAccount 1786 return nil 1787 } 1788 } 1789 1790 // Verify that an order with the same commitment is not already in the epoch 1791 // queue. Since commitment is part of the order serialization and thus order 1792 // ID, this also prevents orders with the same ID. 1793 // TODO: Prevent commitment reuse in general, without expensive DB queries. 1794 ord := rec.order 1795 oid := ord.ID() 1796 user := ord.User() 1797 1798 commit := ord.Commitment() 1799 m.epochMtx.RLock() 1800 otherOid, found := m.epochCommitments[commit] 1801 m.epochMtx.RUnlock() 1802 if found { 1803 log.Debugf("Received order %v with commitment %x also used in previous order %v!", 1804 oid, commit, otherOid) 1805 errChan <- ErrInvalidCommitment 1806 return nil 1807 } 1808 1809 // Verify that another cancel order targeting the same order is not already 1810 // in the epoch queue. Market and limit orders using the same coin IDs as 1811 // other orders is prevented by the coinlocker. 1812 epochGap := db.EpochGapNA 1813 if co, ok := ord.(*order.CancelOrder); ok { 1814 if eco := epoch.CancelTargets[co.TargetOrderID]; eco != nil { 1815 log.Debugf("Received cancel order %v targeting %v, but already have %v.", 1816 co, co.TargetOrderID, eco) 1817 errChan <- ErrDuplicateCancelOrder 1818 return nil 1819 } 1820 1821 if nc := epoch.UserCancels[co.AccountID]; nc >= m.marketInfo.MaxUserCancelsPerEpoch { 1822 log.Debugf("Received cancel order %v targeting %v, but user already has %d cancel orders in this epoch.", 1823 co, co.TargetOrderID, nc) 1824 errChan <- ErrTooManyCancelOrders 1825 return nil 1826 } 1827 1828 // Verify that the target order is on the books or in the epoch queue, 1829 // and that the account of the CancelOrder is the same as the account of 1830 // the target order. 1831 cancelable, loTime, err := m.CancelableBy(co.TargetOrderID, co.AccountID) 1832 if !cancelable { 1833 log.Debugf("Cancel order %v (account=%v) target order %v: %v", 1834 co, co.AccountID, co.TargetOrderID, err) 1835 errChan <- err 1836 return nil 1837 } 1838 1839 epochGap = int32(epoch.Epoch - loTime.UnixMilli()/epoch.Duration) 1840 1841 } else { // Not a cancel order, check user limits. 1842 likelyTaker, baseQty := m.analysisHelpers() 1843 orderWeight := baseQty(ord) 1844 if likelyTaker(ord) { 1845 orderWeight *= 2 1846 } 1847 calcParcels := func(settlingWeight uint64) float64 { 1848 return m.parcels(user, settlingWeight+orderWeight) 1849 } 1850 if !m.checkParcelLimit(user, calcParcels) { 1851 log.Debugf("Received order %s that pushed user over the parcel limit", oid) 1852 errChan <- ErrQuantityTooHigh 1853 return nil 1854 } 1855 } 1856 1857 // Sign the order and prepare the client response. Only after the archiver 1858 // has successfully stored the new epoch order should the order be committed 1859 // for processing. 1860 respMsg, err := m.orderResponse(rec) 1861 if err != nil { 1862 log.Errorf("failed to create msgjson.Message for order %v, msgID %v response: %v", 1863 ord, rec.msgID, err) 1864 errChan <- ErrMalformedOrderResponse 1865 return nil 1866 } 1867 1868 // Ensure that the received order does not use locked coins. 1869 if lockedCoins, assetID := m.coinsLocked(ord); len(lockedCoins) > 0 { 1870 log.Debugf("processOrder: Order %v submitted with already-locked %s coins: %v", 1871 ord, strings.ToUpper(dex.BipIDSymbol(assetID)), fmtCoinIDs(assetID, lockedCoins)) 1872 errChan <- ErrInvalidOrder 1873 return nil 1874 } 1875 1876 // For market and limit orders, lock the backing coins NOW so orders using 1877 // locked coins cannot get into the epoch queue. Later, in processReadyEpoch 1878 // or the Swapper, release these coins when the swap is completed. 1879 m.lockOrderCoins(ord) 1880 1881 // Check for known orders in the DB with the same Commitment. 1882 // 1883 // NOTE: This is disabled since (1) it may not scale as order history grows, 1884 // and (2) it is hard to see how this can be done by new servers in a mesh. 1885 // NOTE 2: Perhaps a better check would be commits with revealed preimages, 1886 // since a dedicated commit->preimage map or DB is conceivable. 1887 // 1888 // commitFound, prevOrderID, err := m.storage.OrderWithCommit(ctx, commit) 1889 // if err != nil { 1890 // errChan <- ErrInternalServer 1891 // return fmt.Errorf("processOrder: Failed to query for orders by commitment: %v", err) 1892 // } 1893 // if commitFound { 1894 // log.Debugf("processOrder: Order %v submitted with reused commitment %v "+ 1895 // "from previous order %v", ord, commit, prevOrderID) 1896 // errChan <- ErrInvalidCommitment 1897 // return nil 1898 // } 1899 1900 // Store the new epoch order BEFORE inserting it into the epoch queue, 1901 // initiating the swap, and notifying book subscribers. 1902 if err := m.storage.NewEpochOrder(ord, epoch.Epoch, epoch.Duration, epochGap); err != nil { 1903 errChan <- ErrInternalServer 1904 return fmt.Errorf("processOrder: Failed to store new epoch order %v: %w", 1905 ord, err) 1906 } 1907 1908 // Insert the order into the epoch queue. 1909 epoch.Insert(ord) 1910 1911 m.epochMtx.Lock() 1912 m.epochOrders[oid] = ord 1913 m.epochCommitments[commit] = oid 1914 m.epochMtx.Unlock() 1915 1916 // Respond to the order router only after updating epochOrders so that 1917 // Cancelable will reflect that the order is now in the epoch queue. 1918 errChan <- nil 1919 1920 // Inform the client that the order has been received, stamped, signed, and 1921 // inserted into the current epoch queue. 1922 m.lazy(func() { 1923 if err := m.auth.Send(user, respMsg); err != nil { 1924 log.Infof("Failed to send signed new order response to user %v, order %v: %v", 1925 user, oid, err) 1926 } 1927 }) 1928 1929 // Send epoch update to epoch queue subscribers. 1930 notifyChan <- &updateSignal{ 1931 action: epochAction, 1932 data: sigDataEpochOrder{ 1933 order: ord, 1934 epochIdx: epoch.Epoch, 1935 }, 1936 } 1937 // With the notification sent to subscribers, this order must be included in 1938 // the processing of this epoch. 1939 return nil 1940 } 1941 1942 func idToBytes(id [order.OrderIDSize]byte) []byte { 1943 return id[:] 1944 } 1945 1946 // respondError sends an rpcError to a user. 1947 func (m *Market) respondError(id uint64, user account.AccountID, code int, errMsg string) { 1948 log.Debugf("sending error to user %v, code: %d, msg: %s", user, code, errMsg) 1949 msg, err := msgjson.NewResponse(id, nil, &msgjson.Error{ 1950 Code: code, 1951 Message: errMsg, 1952 }) 1953 if err != nil { 1954 log.Errorf("error creating error response with message '%s': %v", msg, err) 1955 } 1956 if err := m.auth.Send(user, msg); err != nil { 1957 log.Infof("Failed to send %s error response (code = %d, msg = %s) to user %v: %v", 1958 msg.Route, code, errMsg, user, err) 1959 } 1960 } 1961 1962 // preimage request-response handling data 1963 type piData struct { 1964 ord order.Order 1965 preimage chan *order.Preimage 1966 } 1967 1968 // handlePreimageResp is to be used in the response callback function provided 1969 // to AuthManager.Request for the preimage route. 1970 func (m *Market) handlePreimageResp(msg *msgjson.Message, reqData *piData) { 1971 sendPI := func(pi *order.Preimage) { 1972 reqData.preimage <- pi 1973 } 1974 1975 var piResp msgjson.PreimageResponse 1976 resp, err := msg.Response() 1977 if err != nil { 1978 sendPI(nil) 1979 m.respondError(msg.ID, reqData.ord.User(), msgjson.RPCParseError, 1980 fmt.Sprintf("error parsing preimage notification response: %v", err)) 1981 return 1982 } 1983 if resp.Error != nil { 1984 log.Warnf("Client failed to handle preimage request: %v", resp.Error) 1985 sendPI(nil) 1986 return 1987 } 1988 err = json.Unmarshal(resp.Result, &piResp) 1989 if err != nil { 1990 sendPI(nil) 1991 m.respondError(msg.ID, reqData.ord.User(), msgjson.RPCParseError, 1992 fmt.Sprintf("error parsing preimage response payload result: %v", err)) 1993 return 1994 } 1995 1996 // Validate preimage length. 1997 if len(piResp.Preimage) != order.PreimageSize { 1998 sendPI(nil) 1999 m.respondError(msg.ID, reqData.ord.User(), msgjson.InvalidPreimage, 2000 fmt.Sprintf("invalid preimage length (%d byes)", len(piResp.Preimage))) 2001 return 2002 } 2003 2004 // Check that the preimage is the hash of the order commitment. 2005 var pi order.Preimage 2006 copy(pi[:], piResp.Preimage) 2007 piCommit := pi.Commit() 2008 if reqData.ord.Commitment() != piCommit { 2009 sendPI(nil) 2010 oc := reqData.ord.Commitment() 2011 m.respondError(msg.ID, reqData.ord.User(), msgjson.PreimageCommitmentMismatch, 2012 fmt.Sprintf("preimage hash %x does not match order commitment %x", 2013 piCommit[:], oc[:])) 2014 return 2015 } 2016 2017 // The preimage is good. 2018 log.Tracef("Good preimage received for order %v: %x", reqData.ord, pi) 2019 err = m.storage.StorePreimage(reqData.ord, pi) 2020 if err != nil { 2021 log.Errorf("StorePreimage: %v", err) 2022 // Fatal backend error. New swaps will not begin, but pass the preimage 2023 // along so that it does not appear as a miss to collectPreimages. 2024 m.respondError(msg.ID, reqData.ord.User(), msgjson.RPCInternalError, 2025 "internal server error") 2026 } 2027 2028 sendPI(&pi) 2029 } 2030 2031 // collectPreimages solicits preimages from the owners of each of the orders in 2032 // the provided queue with a 'preimage' ntfn/request via AuthManager.Request, 2033 // and returns the preimages contained in the client responses. This function 2034 // can block for up to 20 seconds (piTimeout) to allow clients time to respond. 2035 // Clients that fail to respond, or respond with invalid data (see 2036 // handlePreimageResp), are counted as misses. 2037 func (m *Market) collectPreimages(orders []order.Order) (cSum []byte, ordersRevealed []*matcher.OrderRevealed, misses []order.Order) { 2038 // Compute the commitment checksum for the order queue. 2039 cSum = matcher.CSum(orders) 2040 2041 // Request preimages from the clients. 2042 piTimeout := 20 * time.Second 2043 preimages := make(map[order.Order]chan *order.Preimage, len(orders)) 2044 for _, ord := range orders { 2045 // Make the 'preimage' request. 2046 commit := ord.Commitment() 2047 piReqParams := &msgjson.PreimageRequest{ 2048 OrderID: idToBytes(ord.ID()), 2049 Commitment: commit[:], 2050 CommitChecksum: cSum, 2051 } 2052 req, err := msgjson.NewRequest(comms.NextID(), msgjson.PreimageRoute, piReqParams) 2053 if err != nil { 2054 // This is likely an impossible condition, but it's not the client's 2055 // fault. 2056 log.Errorf("error creating preimage request: %v", err) 2057 // TODO: respond to client with server error. 2058 continue 2059 } 2060 2061 // The clients preimage response comes back via a channel, where nil 2062 // indicates client failure to respond, either due to disconnection or 2063 // no action. 2064 piChan := make(chan *order.Preimage, 1) // buffer so the link's in handler does not block 2065 2066 reqData := &piData{ 2067 ord: ord, 2068 preimage: piChan, 2069 } 2070 2071 // Failure to respond in time or an async link write error is a miss, 2072 // signalled by a nil pointer. Request errors returned by 2073 // RequestWithTimeout instead register a miss immediately. 2074 miss := func() { piChan <- nil } 2075 2076 // Send the preimage request to the order's owner. 2077 err = m.auth.RequestWithTimeout(ord.User(), req, func(_ comms.Link, msg *msgjson.Message) { 2078 m.handlePreimageResp(msg, reqData) // sends on piChan 2079 }, piTimeout, miss) 2080 if err != nil { 2081 if errors.Is(err, ws.ErrPeerDisconnected) || errors.Is(err, auth.ErrUserNotConnected) { 2082 log.Debugf("Preimage request failed, client gone: %v", err) 2083 } else { 2084 // We may need a way to identify server connectivity problems so 2085 // clients are not penalized when it is not their fault. For 2086 // now, log this at warning level since the error is not novel. 2087 log.Warnf("Preimage request failed: %v", err) 2088 } 2089 2090 // Register the miss now, no channel receive for this order. 2091 misses = append(misses, ord) 2092 continue 2093 } 2094 2095 log.Tracef("Preimage request sent for order %v", ord) 2096 preimages[ord] = piChan 2097 } 2098 2099 // Receive preimages from response channels. 2100 for ord, pic := range preimages { 2101 pi := <-pic 2102 if pi == nil { 2103 misses = append(misses, ord) 2104 } else { 2105 ordersRevealed = append(ordersRevealed, &matcher.OrderRevealed{ 2106 Order: ord, 2107 Preimage: *pi, 2108 }) 2109 } 2110 } 2111 2112 return 2113 } 2114 2115 func (m *Market) enqueueEpoch(eq *epochPump, epoch *EpochQueue) bool { 2116 // Enqueue the epoch for matching when preimage collection is completed and 2117 // it is this epoch's turn. 2118 rq := eq.Insert(epoch) 2119 if rq == nil { 2120 // should not happen if cycleEpoch considers when the halt began. 2121 log.Errorf("failed to enqueue an epoch into a halted epoch pump") 2122 return false 2123 } 2124 2125 // With this epoch closed, these orders are no longer cancelable, if and 2126 // until they are booked in processReadyEpoch (after preimage collection). 2127 orders := epoch.OrderSlice() 2128 m.epochMtx.Lock() 2129 for _, ord := range orders { 2130 delete(m.epochOrders, ord.ID()) 2131 delete(m.epochCommitments, ord.Commitment()) 2132 // Would be nice to remove orders from users that got suspended, but the 2133 // epoch order notifications were sent to subscribers when the order was 2134 // received, thus setting expectations for auditing the queue. 2135 // 2136 // Preimage collection for suspended users could be skipped, forcing 2137 // them into the misses slice perhaps by passing user IDs to skip into 2138 // prepEpoch, with a SPEC UPDATE noting that preimage requests are not 2139 // sent to suspended accounts. 2140 } 2141 m.epochMtx.Unlock() 2142 2143 // Start preimage collection. 2144 go func() { 2145 rq.cSum, rq.ordersRevealed, rq.misses = m.prepEpoch(orders, epoch.End) 2146 close(rq.ready) 2147 }() 2148 2149 return true 2150 } 2151 2152 func (m *Market) sendRevokeOrderNote(oid order.OrderID, user account.AccountID) { 2153 // Send revoke_order notification to order owner. 2154 route := msgjson.RevokeOrderRoute 2155 log.Infof("Sending a '%s' notification to %v for order %v", route, user, oid) 2156 revMsg := &msgjson.RevokeOrder{ 2157 OrderID: oid.Bytes(), 2158 } 2159 m.auth.Sign(revMsg) 2160 revNtfn, err := msgjson.NewNotification(route, revMsg) 2161 if err != nil { 2162 log.Errorf("Failed to create %s notification for order %v: %v", route, oid, err) 2163 } else { 2164 err = m.auth.Send(user, revNtfn) 2165 if err != nil { 2166 log.Debugf("Failed to send %s notification to user %v: %v", route, user, err) 2167 } 2168 } 2169 } 2170 2171 // prepEpoch collects order preimages, and penalizes users who fail to respond. 2172 func (m *Market) prepEpoch(orders []order.Order, epochEnd time.Time) (cSum []byte, ordersRevealed []*matcher.OrderRevealed, misses []order.Order) { 2173 // Solicit the preimages for each order. 2174 cSum, ordersRevealed, misses = m.collectPreimages(orders) 2175 if len(orders) > 0 { 2176 log.Infof("Collected %d valid order preimages, missed %d. Commit checksum: %x", 2177 len(ordersRevealed), len(misses), cSum) 2178 } 2179 2180 for _, ord := range misses { 2181 oid, user := ord.ID(), ord.User() 2182 log.Infof("No preimage received for order %v from user %v. Recording violation and revoking order.", 2183 oid, user) 2184 // Unlock the order's coins locked in processOrder. 2185 m.unlockOrderCoins(ord) // could also be done in processReadyEpoch 2186 // Change the order status from orderStatusEpoch to orderStatusRevoked. 2187 coid, revTime, err := m.storage.RevokeOrder(ord) 2188 if err == nil { 2189 m.auth.RecordCancel(user, coid, oid, db.EpochGapNA, revTime) 2190 } else { 2191 log.Errorf("Failed to revoke order %v with a new cancel order: %v", 2192 ord.UID(), err) 2193 } 2194 // Register the preimage miss violation, adjusting the user's score. 2195 m.auth.MissedPreimage(user, epochEnd, oid) 2196 // The user is most likely offline, but it is possible they have 2197 // reconnected too late for the preimage request but after 2198 // storage.RevokeOrder updated the order status. Try to notify. 2199 go m.sendRevokeOrderNote(oid, user) 2200 } 2201 2202 // Register the preimage collection successes, potentially evicting preimage 2203 // miss violations for purposes of user scoring. 2204 for _, ord := range ordersRevealed { 2205 m.auth.PreimageSuccess(ord.Order.User(), epochEnd, ord.Order.ID()) 2206 } 2207 2208 return 2209 } 2210 2211 // UnbookUserOrders unbooks all orders belonging to a user, unlocks the coins 2212 // that were used to fund the unbooked orders, changes the orders' statuses to 2213 // revoked in the DB, and notifies orderbook subscribers. 2214 func (m *Market) UnbookUserOrders(user account.AccountID) { 2215 m.bookMtx.Lock() 2216 removedBuys, removedSells := m.book.RemoveUserOrders(user) 2217 // No order completion credit in SwapDone for revoked orders: 2218 for _, lo := range removedSells { 2219 delete(m.settling, lo.ID()) 2220 } 2221 for _, lo := range removedBuys { 2222 delete(m.settling, lo.ID()) 2223 } 2224 m.bookMtx.Unlock() 2225 2226 total := len(removedBuys) + len(removedSells) 2227 if total == 0 { 2228 return 2229 } 2230 2231 log.Infof("Unbooked %d orders (%d buys, %d sells) from market %v from user %v.", 2232 total, len(removedBuys), len(removedSells), m.marketInfo.Name, user) 2233 2234 // Unlock the order funding coins, update order statuses in DB, and notify 2235 // orderbook subscribers. 2236 sellIDs := make([]order.OrderID, 0, len(removedSells)) 2237 for _, lo := range removedSells { 2238 sellIDs = append(sellIDs, lo.ID()) 2239 m.unbookedOrder(lo) 2240 } 2241 if m.coinLockerBase != nil { 2242 m.coinLockerBase.UnlockOrdersCoins(sellIDs) 2243 } 2244 2245 buyIDs := make([]order.OrderID, 0, len(removedBuys)) 2246 for _, lo := range removedBuys { 2247 buyIDs = append(buyIDs, lo.ID()) 2248 m.unbookedOrder(lo) 2249 } 2250 if m.coinLockerQuote != nil { 2251 m.coinLockerQuote.UnlockOrdersCoins(buyIDs) 2252 } 2253 } 2254 2255 // Unbook allows the DEX manager to remove a booked order. This does: (1) remove 2256 // the order from the in-memory book, (2) unlock funding order coins, (3) set 2257 // the order's status in the DB to "revoked", (4) inform the auth manager of the 2258 // action for cancellation ratio accounting, and (5) send an 'unbook' 2259 // notification to subscribers of this market's order book. Note that this 2260 // presently treats the user as at-fault by counting the revocation in the 2261 // user's cancellation statistics. 2262 func (m *Market) Unbook(lo *order.LimitOrder) bool { 2263 // Ensure we do not unbook during matching. 2264 m.bookMtx.Lock() 2265 _, removed := m.book.Remove(lo.ID()) 2266 delete(m.settling, lo.ID()) // no order completion credit in SwapDone for revoked orders 2267 m.bookMtx.Unlock() 2268 2269 m.unlockOrderCoins(lo) 2270 2271 if removed { 2272 // Update the order status in DB, and notify orderbook subscribers. 2273 m.unbookedOrder(lo) 2274 } 2275 return removed 2276 } 2277 2278 func (m *Market) unbookedOrder(lo *order.LimitOrder) { 2279 // Create the server-generated cancel order, and register it with the 2280 // AuthManager for cancellation rate computation if still connected. 2281 oid, user := lo.ID(), lo.User() 2282 coid, revTime, err := m.storage.RevokeOrder(lo) 2283 if err == nil { 2284 m.auth.RecordCancel(user, coid, oid, db.EpochGapNA, revTime) 2285 } else { 2286 log.Errorf("Failed to revoke order %v with a new cancel order: %v", 2287 lo.UID(), err) 2288 } 2289 2290 // Send revoke_order notification to order owner. 2291 m.sendRevokeOrderNote(oid, user) 2292 2293 // Send "unbook" notification to order book subscribers. 2294 m.sendToFeeds(&updateSignal{ 2295 action: unbookAction, 2296 data: sigDataUnbookedOrder{ 2297 order: lo, 2298 epochIdx: -1, // NOTE: no epoch 2299 }, 2300 }) 2301 } 2302 2303 // getFeeRate gets the fee rate for an asset. 2304 func (m *Market) getFeeRate(assetID uint32, f FeeFetcher) uint64 { 2305 // Do not block indefinitely waiting for fetcher. 2306 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 2307 defer cancel() 2308 rate := f.SwapFeeRate(ctx) 2309 if ctx.Err() != nil { // timeout, try last known rate 2310 rate = f.LastRate() 2311 log.Warnf("Failed to get latest fee rate for %v. Using last known rate %d.", 2312 dex.BipIDSymbol(assetID), rate) 2313 } 2314 rate = m.ScaleFeeRate(assetID, rate) 2315 if rate > f.MaxFeeRate() || rate == 0 { 2316 rate = f.MaxFeeRate() 2317 } 2318 return rate 2319 } 2320 2321 // processReadyEpoch performs the following operations for a closed epoch that 2322 // has finished preimage collection via collectPreimages: 2323 // 1. Perform matching with the order book. 2324 // 2. Send book and unbook notifications to the book subscribers. 2325 // 3. Unlock coins with the book lock for unbooked and failed orders. 2326 // 4. Lock coins with the swap lock. 2327 // 5. Initiate the swap negotiation via the Market's Swapper. 2328 // 2329 // The EpochQueue's Orders map must not be modified by another goroutine. 2330 func (m *Market) processReadyEpoch(epoch *readyEpoch, notifyChan chan<- *updateSignal) { 2331 // Ensure the epoch has actually completed preimage collection. This can 2332 // only fail if the epochPump malfunctioned. Remove this check eventually. 2333 select { 2334 case <-epoch.ready: 2335 default: 2336 log.Criticalf("preimages not yet collected for epoch %d!", epoch.Epoch) 2337 return // maybe panic 2338 } 2339 2340 // Abort epoch processing if there was a fatal DB backend error during 2341 // preimage collection. 2342 if err := m.storage.LastErr(); err != nil { 2343 log.Criticalf("aborting epoch processing on account of failing DB: %v", err) 2344 return 2345 } 2346 2347 // Get the base and quote fee rates. 2348 // NOTE: We might consider moving this before the match cycle and abandoning 2349 // the match cycle when no fee rate can be found (on mainnet). The only 2350 // hesitation there is that it makes certain maintenance tasks longer but 2351 // that's not unexpected in the world of cryptocurrency exchanges. It also 2352 // makes it harder to fire up a private DEX server to conduct a private 2353 // trade, but even in that case, I wouldn't want to be matching at the 2354 // fallback MaxFeeRate when the justifiable network rate is much lower. I do 2355 // remember some minor discussion of this at some point in the past, but I'd 2356 // like to bring it back. 2357 feeRateBase := m.getFeeRate(m.Base(), m.baseFeeFetcher) 2358 feeRateQuote := m.getFeeRate(m.Quote(), m.quoteFeeFetcher) 2359 2360 // Data from preimage collection 2361 ordersRevealed := epoch.ordersRevealed 2362 cSum := epoch.cSum 2363 misses := epoch.misses 2364 2365 // We can't call RecordCancel under the bookMtx since it can potentially 2366 // trigger a user suspension and unbooking via UnbookUserOrders, which locks 2367 // the bookMtx. So we'll track the info necessary to call RecordCancel and 2368 // call them after the matches loop. 2369 type cancelMatch struct { 2370 co *order.CancelOrder 2371 loEpoch int64 2372 } 2373 cancelMatches := make([]cancelMatch, 0) 2374 2375 // Perform order matching using the preimages to shuffle the queue. 2376 m.bookMtx.Lock() // allow a coherent view of book orders with (*Market).Book 2377 matchTime := time.Now() // considered as the time at which matched cancel orders are executed 2378 seed, matches, _, failed, doneOK, partial, booked, nomatched, unbooked, updates, stats := m.matcher.Match(m.book, ordersRevealed) 2379 m.bookEpochIdx = epoch.Epoch + 1 2380 epochDur := int64(m.EpochDuration()) 2381 var canceled []order.OrderID 2382 for _, ms := range matches { 2383 // Set the epoch ID. 2384 ms.Epoch.Idx = uint64(epoch.Epoch) 2385 ms.Epoch.Dur = uint64(epoch.Duration) 2386 ms.FeeRateBase = feeRateBase 2387 ms.FeeRateQuote = feeRateQuote 2388 2389 // Update order settling amounts. 2390 for _, match := range ms.Matches() { 2391 if co, ok := match.Taker.(*order.CancelOrder); ok { 2392 canceled = append(canceled, co.TargetOrderID) 2393 cancelMatches = append(cancelMatches, cancelMatch{ 2394 co: co, 2395 loEpoch: match.Maker.ServerTime.UnixMilli() / epochDur, 2396 }) 2397 continue 2398 } 2399 m.settling[match.Taker.ID()] += match.Quantity 2400 m.settling[match.Maker.ID()] += match.Quantity 2401 } 2402 } 2403 for _, oid := range canceled { 2404 // There may still be swaps settling, but we don't care anymore because 2405 // there is no completion credit on a canceled order. 2406 delete(m.settling, oid) 2407 } 2408 m.bookMtx.Unlock() 2409 2410 if len(ordersRevealed) > 0 { 2411 log.Infof("Matching complete for market %v epoch %d:"+ 2412 " %d matches (%d partial fills), %d completed OK (not booked),"+ 2413 " %d booked, %d unbooked, %d failed", 2414 m.marketInfo.Name, epoch.Epoch, 2415 len(matches), len(partial), len(doneOK), 2416 len(booked), len(unbooked), len(failed), 2417 ) 2418 } 2419 2420 // Store data in epochs table, including matchTime so that cancel execution 2421 // times can be obtained from the DB for cancellation rate computation. 2422 oidsRevealed := make([]order.OrderID, 0, len(ordersRevealed)) 2423 for _, or := range ordersRevealed { 2424 oidsRevealed = append(oidsRevealed, or.Order.ID()) 2425 } 2426 oidsMissed := make([]order.OrderID, 0, len(misses)) 2427 for _, om := range misses { 2428 oidsMissed = append(oidsMissed, om.ID()) 2429 } 2430 2431 // If there were no matches, we need to persist that last rate from the last 2432 // match recorded. 2433 if stats.EndRate == 0 { 2434 stats.EndRate = m.lastRate 2435 stats.StartRate = m.lastRate 2436 stats.HighRate = m.lastRate 2437 stats.LowRate = m.lastRate 2438 } else { 2439 m.lastRate = stats.EndRate 2440 } 2441 2442 err := m.storage.InsertEpoch(&db.EpochResults{ 2443 MktBase: m.marketInfo.Base, 2444 MktQuote: m.marketInfo.Quote, 2445 Idx: epoch.Epoch, 2446 Dur: epoch.Duration, 2447 MatchTime: matchTime.UnixMilli(), 2448 CSum: cSum, 2449 Seed: seed, 2450 OrdersRevealed: oidsRevealed, 2451 OrdersMissed: oidsMissed, 2452 MatchVolume: stats.MatchVolume, 2453 QuoteVolume: stats.QuoteVolume, 2454 BookBuys: stats.BookBuys, 2455 BookBuys5: stats.BookBuys5, 2456 BookBuys25: stats.BookBuys25, 2457 BookSells: stats.BookSells, 2458 BookSells5: stats.BookSells5, 2459 BookSells25: stats.BookSells25, 2460 HighRate: stats.HighRate, 2461 LowRate: stats.LowRate, 2462 StartRate: stats.StartRate, 2463 EndRate: stats.EndRate, 2464 }) 2465 if err != nil { 2466 // fatal backend error, do not begin new swaps. 2467 return // TODO: notify clients 2468 } 2469 2470 // Note: validated preimages are stored in the orders/cancels tables on 2471 // receipt from the user by handlePreimageResp. 2472 2473 // Update orders in persistent storage. Trade orders may appear in multiple 2474 // trade order slices, so update in the sequence: booked, partial, completed 2475 // or canceled. However, an order in the failed slice will not be in another 2476 // slice since failed indicates unmatched&unbooked or bad lot size. 2477 // 2478 // TODO: Only execute the net effect. Each status update also updates the 2479 // filled amount of the trade order. 2480 // 2481 // Cancel order status updates are from epoch to executed or failed status. 2482 2483 // Newly-booked orders. 2484 for _, lo := range updates.TradesBooked { 2485 if err = m.storage.BookOrder(lo); err != nil { 2486 return 2487 } 2488 } 2489 2490 // Book orders that were partially filled and remain on the books. 2491 for _, lo := range updates.TradesPartial { 2492 if err = m.storage.UpdateOrderFilled(lo); err != nil { 2493 return 2494 } 2495 } 2496 2497 // Completed orders (includes epoch and formerly booked orders). 2498 for _, ord := range updates.TradesCompleted { 2499 if err = m.storage.ExecuteOrder(ord); err != nil { 2500 return 2501 } 2502 } 2503 // Canceled orders. 2504 for _, lo := range updates.TradesCanceled { 2505 if err = m.storage.CancelOrder(lo); err != nil { 2506 return 2507 } 2508 } 2509 // Failed orders refer to epoch queue orders that are unmatched&unbooked, or 2510 // had a bad lot size. 2511 for _, ord := range updates.TradesFailed { 2512 if err = m.storage.ExecuteOrder(ord); err != nil { 2513 return 2514 } 2515 } 2516 2517 // Change cancel orders from epoch status to executed or failed status. 2518 for _, co := range updates.CancelsFailed { 2519 if err = m.storage.FailCancelOrder(co); err != nil { 2520 return 2521 } 2522 } 2523 for _, co := range updates.CancelsExecuted { 2524 if err = m.storage.ExecuteOrder(co); err != nil { 2525 return 2526 } 2527 } 2528 2529 // Signal the match_proof to the orderbook subscribers. 2530 preimages := make([]order.Preimage, len(ordersRevealed)) 2531 for i := range ordersRevealed { 2532 preimages[i] = ordersRevealed[i].Preimage 2533 } 2534 sig := &updateSignal{ 2535 action: matchProofAction, 2536 data: sigDataMatchProof{ 2537 matchProof: &order.MatchProof{ 2538 Epoch: order.EpochID{ 2539 Idx: uint64(epoch.Epoch), 2540 Dur: m.EpochDuration(), 2541 }, 2542 Preimages: preimages, 2543 Misses: misses, 2544 CSum: cSum, 2545 Seed: seed, 2546 }, 2547 }, 2548 } 2549 notifyChan <- sig 2550 2551 // Unlock passed but not booked order (e.g. matched market and immediate 2552 // orders) coins were locked upon order receipt in processOrder and must be 2553 // unlocked now since they do not go on the book. 2554 for _, k := range doneOK { 2555 m.unlockOrderCoins(k.Order) 2556 } 2557 2558 // Unlock unmatched (failed) order coins. 2559 for _, fo := range failed { 2560 m.unlockOrderCoins(fo.Order) 2561 } 2562 2563 // Booked order coins were locked upon receipt by processOrder, and remain 2564 // locked until they are either: unbooked by a future match that completely 2565 // fills the order, unbooked by a matched cancel order, or (unimplemented) 2566 // unbooked by another Market mechanism such as client disconnect or ban. 2567 2568 // Unlock unbooked order coins. 2569 for _, ubo := range unbooked { 2570 m.unlockOrderCoins(ubo) 2571 } 2572 2573 // Send "book" notifications to order book subscribers. 2574 for _, ord := range booked { 2575 sig := &updateSignal{ 2576 action: bookAction, 2577 data: sigDataBookedOrder{ 2578 order: ord.Order, 2579 epochIdx: epoch.Epoch, 2580 }, 2581 } 2582 notifyChan <- sig 2583 } 2584 2585 // Send "update_remaining" notifications to order book subscribers. 2586 for _, lo := range updates.TradesPartial { 2587 notifyChan <- &updateSignal{ 2588 action: updateRemainingAction, 2589 data: sigDataUpdateRemaining{ 2590 order: lo, 2591 epochIdx: epoch.Epoch, 2592 }, 2593 } 2594 } 2595 2596 // Send "unbook" notifications to order book subscribers. This must be after 2597 // update_remaining. 2598 for _, ord := range unbooked { 2599 sig := &updateSignal{ 2600 action: unbookAction, 2601 data: sigDataUnbookedOrder{ 2602 order: ord, 2603 epochIdx: epoch.Epoch, 2604 }, 2605 } 2606 notifyChan <- sig 2607 } 2608 2609 for _, c := range cancelMatches { 2610 co, loEpoch := c.co, c.loEpoch 2611 epochGap := int32((co.ServerTime.UnixMilli() / epochDur) - loEpoch) 2612 m.auth.RecordCancel(co.User(), co.ID(), co.TargetOrderID, epochGap, matchTime) 2613 } 2614 2615 // Send "nomatch" notifications. 2616 for _, ord := range nomatched { 2617 oid := ord.Order.ID() 2618 msg, err := msgjson.NewNotification(msgjson.NoMatchRoute, &msgjson.NoMatch{ 2619 OrderID: oid[:], 2620 }) 2621 if err != nil { 2622 // This is probably impossible in practice, but we'll log it anyway. 2623 log.Errorf("Failed to encode 'nomatch' notification.") 2624 continue 2625 } 2626 if err := m.auth.Send(ord.Order.User(), msg); err != nil { 2627 log.Infof("Failed to send nomatch to user %s: %v", ord.Order.User(), err) 2628 } 2629 } 2630 2631 // Update the API data collector. 2632 spot, err := m.dataCollector.ReportEpoch(m.Base(), m.Quote(), uint64(epoch.Epoch), stats) 2633 if err != nil { 2634 log.Errorf("Error updating API data collector: %v", err) 2635 } 2636 2637 matchReport := make([][2]int64, 0, len(matches)) 2638 var lastRate uint64 2639 var lastSide bool 2640 for _, matchSet := range matches { 2641 for _, match := range matchSet.Matches() { 2642 t := match.Taker.Trade() 2643 if t == nil { 2644 continue 2645 } 2646 if match.Rate != lastRate || t.Sell != lastSide { 2647 matchReport = append(matchReport, [2]int64{int64(match.Rate), 0}) 2648 lastRate, lastSide = match.Rate, t.Sell 2649 } 2650 if t.Sell { 2651 matchReport[len(matchReport)-1][1] += int64(match.Quantity) 2652 } else { 2653 matchReport[len(matchReport)-1][1] -= int64(match.Quantity) 2654 } 2655 } 2656 } 2657 // Send "epoch_report" notifications. 2658 notifyChan <- &updateSignal{ 2659 action: epochReportAction, 2660 data: sigDataEpochReport{ 2661 epochIdx: epoch.Epoch, 2662 epochDur: epoch.Duration, 2663 spot: spot, 2664 stats: stats, 2665 baseFeeRate: feeRateBase, 2666 quoteFeeRate: feeRateQuote, 2667 matches: matchReport, 2668 }, 2669 } 2670 2671 // Initiate the swaps. 2672 if len(matches) > 0 { 2673 log.Debugf("Negotiating %d matches for epoch %d:%d", len(matches), 2674 epoch.Epoch, epoch.Duration) 2675 m.swapper.Negotiate(matches) 2676 } 2677 } 2678 2679 // validateOrder uses db.ValidateOrder to ensure that the provided order is 2680 // valid for the current market with epoch order status. 2681 func (m *Market) validateOrder(ord order.Order) error { 2682 // First check the order commitment before bothering the Market's run loop. 2683 c0 := order.Commitment{} 2684 if ord.Commitment() == c0 { 2685 // Note that OrderID may not be valid if ServerTime has not been set. 2686 return ErrInvalidCommitment 2687 } 2688 2689 if !db.ValidateOrder(ord, order.OrderStatusEpoch, m.marketInfo) { 2690 return ErrInvalidOrder // non-specific 2691 } 2692 2693 if lo, is := ord.(*order.LimitOrder); is && lo.Rate < m.minimumRate { 2694 return ErrInvalidRate 2695 } 2696 2697 return nil 2698 } 2699 2700 // orderResponse signs the order data and prepares the OrderResult to be sent to 2701 // the client. 2702 func (m *Market) orderResponse(oRecord *orderRecord) (*msgjson.Message, error) { 2703 // Add the server timestamp. 2704 stamp := uint64(oRecord.order.Time()) 2705 oRecord.req.Stamp(stamp) 2706 2707 // Sign the serialized order request. 2708 m.auth.Sign(oRecord.req) 2709 2710 // Prepare the OrderResult, including the server signature and time stamp. 2711 oid := oRecord.order.ID() 2712 res := &msgjson.OrderResult{ 2713 Sig: oRecord.req.SigBytes(), 2714 OrderID: oid[:], 2715 ServerTime: stamp, 2716 } 2717 2718 // Encode the order response as a message for the client. 2719 return msgjson.NewResponse(oRecord.msgID, res, nil) 2720 } 2721 2722 // SetFeeRateScale sets a swap fee scale factor for the given asset. 2723 // SetFeeRateScale should be called regardless of whether the Market is 2724 // suspended. 2725 func (m *Market) SetFeeRateScale(assetID uint32, scale float64) { 2726 m.feeScalesMtx.Lock() 2727 switch assetID { 2728 case m.marketInfo.Base: 2729 m.feeScales.base = scale 2730 case m.marketInfo.Quote: 2731 m.feeScales.quote = scale 2732 default: 2733 log.Errorf("Unknown asset ID %d for market %d-%d", 2734 assetID, m.marketInfo.Base, m.marketInfo.Quote) 2735 } 2736 m.feeScalesMtx.Unlock() 2737 } 2738 2739 // ScaleFeeRate scales the provided fee rate with the given asset's swap fee 2740 // rate scale factor, which is 1.0 by default. 2741 func (m *Market) ScaleFeeRate(assetID uint32, feeRate uint64) uint64 { 2742 if feeRate == 0 { 2743 return feeRate // no idea if this is sensible for any asset, but ok 2744 } 2745 var feeScale float64 2746 m.feeScalesMtx.RLock() 2747 switch assetID { 2748 case m.marketInfo.Base: 2749 feeScale = m.feeScales.base 2750 default: 2751 feeScale = m.feeScales.quote 2752 } 2753 m.feeScalesMtx.RUnlock() 2754 if feeScale == 0 { 2755 return feeRate 2756 } 2757 if feeScale < 1 { 2758 log.Warnf("Using fee rate scale of %f < 1.0 for asset %d", feeScale, assetID) 2759 } 2760 // It started non-zero, so don't allow it to go to zero. 2761 return uint64(math.Max(1.0, math.Round(float64(feeRate)*feeScale))) 2762 } 2763 2764 type accountStats struct { 2765 qty, lots uint64 2766 redeems int 2767 } 2768 2769 type accountCounter map[string]*accountStats 2770 2771 func (a accountCounter) add(addr string, qty, lots uint64, redeems int) { 2772 stats, found := a[addr] 2773 if !found { 2774 stats = new(accountStats) 2775 a[addr] = stats 2776 } 2777 stats.qty += qty 2778 stats.lots += lots 2779 stats.redeems += redeems 2780 }