decred.org/dcrdex@v1.0.3/client/mm/event_log.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 mm 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/binary" 10 "encoding/json" 11 "fmt" 12 13 "decred.org/dcrdex/client/asset" 14 "decred.org/dcrdex/dex" 15 "decred.org/dcrdex/dex/encode" 16 "go.etcd.io/bbolt" 17 ) 18 19 // DEXOrderEvent represents a a dex order that a bot placed. 20 type DEXOrderEvent struct { 21 ID string `json:"id"` 22 Rate uint64 `json:"rate"` 23 Qty uint64 `json:"qty"` 24 Sell bool `json:"sell"` 25 Transactions []*asset.WalletTransaction `json:"transactions"` 26 } 27 28 // CEXOrderEvent represents a a cex order that a bot placed. 29 type CEXOrderEvent struct { 30 ID string `json:"id"` 31 Rate uint64 `json:"rate"` 32 Qty uint64 `json:"qty"` 33 Sell bool `json:"sell"` 34 BaseFilled uint64 `json:"baseFilled"` 35 QuoteFilled uint64 `json:"quoteFilled"` 36 } 37 38 // DepositEvent represents a deposit that a bot made. 39 type DepositEvent struct { 40 Transaction *asset.WalletTransaction `json:"transaction"` 41 AssetID uint32 `json:"assetID"` 42 CEXCredit uint64 `json:"cexCredit"` 43 } 44 45 // WithdrawalEvent represents a withdrawal that a bot made. 46 type WithdrawalEvent struct { 47 ID string `json:"id"` 48 AssetID uint32 `json:"assetID"` 49 Transaction *asset.WalletTransaction `json:"transaction"` 50 CEXDebit uint64 `json:"cexDebit"` 51 } 52 53 // MarketMakingEvent represents an action that a market making bot takes. 54 type MarketMakingEvent struct { 55 ID uint64 `json:"id"` 56 TimeStamp int64 `json:"timestamp"` 57 Pending bool `json:"pending"` 58 BalanceEffects *BalanceEffects `json:"balanceEffects,omitempty"` 59 60 // Only one of the following will be populated. 61 DEXOrderEvent *DEXOrderEvent `json:"dexOrderEvent,omitempty"` 62 CEXOrderEvent *CEXOrderEvent `json:"cexOrderEvent,omitempty"` 63 DepositEvent *DepositEvent `json:"depositEvent,omitempty"` 64 WithdrawalEvent *WithdrawalEvent `json:"withdrawalEvent,omitempty"` 65 UpdateConfig *BotConfig `json:"updateConfig,omitempty"` 66 UpdateInventory *map[uint32]int64 `json:"updateInventory,omitempty"` 67 } 68 69 // MarketMakingRun identifies a market making run. 70 type MarketMakingRun struct { 71 StartTime int64 `json:"startTime"` 72 Market *MarketWithHost `json:"market"` 73 } 74 75 // BalanceState contains the fiat rates and bot balances at the latest point of 76 // a run. 77 type BalanceState struct { 78 FiatRates map[uint32]float64 `json:"fiatRates"` 79 Balances map[uint32]*BotBalance `json:"balances"` 80 InventoryMods map[uint32]int64 `json:"invMods"` 81 } 82 83 // CfgUpdate contains a BotConfig and the timestamp the bot started to use this 84 // configuration. 85 type CfgUpdate struct { 86 Timestamp int64 `json:"timestamp"` 87 Cfg *BotConfig `json:"cfg"` 88 } 89 90 // MarketMakingRunOverview contains information about a market making run. 91 type MarketMakingRunOverview struct { 92 EndTime *int64 `json:"endTime,omitempty"` 93 Cfgs []*CfgUpdate `json:"cfgs"` 94 InitialBalances map[uint32]uint64 `json:"initialBalances"` 95 ProfitLoss *ProfitLoss `json:"profitLoss"` 96 FinalState *BalanceState `json:"finalState"` 97 } 98 99 // eventLogDB is the interface for the event log database. 100 type eventLogDB interface { 101 // storeNewRun stores a new run in the database. It should be called 102 // whenever a new run is started. Events cannot be stored for a run 103 // before this is called. 104 storeNewRun(startTime int64, mkt *MarketWithHost, cfg *BotConfig, initialState *BalanceState) error 105 // storeEvent stores/updates a market making event. 106 storeEvent(startTime int64, mkt *MarketWithHost, e *MarketMakingEvent, fs *BalanceState) 107 // endRun stores the time that a market making run was ended. 108 endRun(startTime int64, mkt *MarketWithHost, endTime int64) error 109 // runs returns a list of runs in the database. If n == 0, all of the runs 110 // will be returned. If refStartTime and refMkt are not nil, the runs 111 // including and before the run with the start time and market will be 112 // returned. 113 runs(n uint64, refStartTime *uint64, refMkt *MarketWithHost) ([]*MarketMakingRun, error) 114 // runOverview returns overview information about a run, not including the 115 // events that took place. 116 runOverview(startTime int64, mkt *MarketWithHost) (*MarketMakingRunOverview, error) 117 // runEvents returns the events that took place during a run. If n == 0, 118 // all of the events will be returned. If refID is not nil, the events 119 // including and after the event with the ID will be returned. If 120 // pendingOnly is true, only pending events will be returned. 121 runEvents(startTime int64, mkt *MarketWithHost, n uint64, refID *uint64, pendingOnly bool, filters *RunLogFilters) ([]*MarketMakingEvent, error) 122 } 123 124 // eventUpdate is used to asynchronously add events to the event log. 125 type eventUpdate struct { 126 runKey []byte 127 e *MarketMakingEvent 128 bs *BalanceState 129 } 130 131 type boltEventLogDB struct { 132 *bbolt.DB 133 log dex.Logger 134 eventUpdates chan *eventUpdate 135 } 136 137 var _ eventLogDB = (*boltEventLogDB)(nil) 138 139 /* 140 * Schema: 141 * 142 * - botRuns 143 * - version 144 * - runBucket (<startTime><baseID><quoteID><host>) 145 * - startTime 146 * - endTime 147 * - cfg 148 * - ib 149 * - fs 150 * - cfgs 151 * - <timestamp> -> <cfg> 152 * - events 153 * - <eventID> -> <event> 154 */ 155 156 var ( 157 botRunsBucket = []byte("botRuns") 158 versionKey = []byte("version") 159 eventsBucket = []byte("events") 160 cfgsBucket = []byte("cfgs") 161 162 startTimeKey = []byte("startTime") 163 endTimeKey = []byte("endTime") 164 initialBalsKey = []byte("ib") 165 finalStateKey = []byte("fs") 166 noPendingKey = []byte("np") 167 ) 168 169 const balanceStateDBVersion uint32 = 1 170 171 func newBoltEventLogDB(ctx context.Context, path string, log dex.Logger) (*boltEventLogDB, error) { 172 db, err := bbolt.Open(path, 0600, nil) 173 if err != nil { 174 return nil, err 175 } 176 177 err = db.Update(func(tx *bbolt.Tx) error { 178 _, err := tx.CreateBucketIfNotExists(botRunsBucket) 179 return err 180 }) 181 if err != nil { 182 return nil, err 183 } 184 185 eventUpdates := make(chan *eventUpdate, 128) 186 eventLogDB := &boltEventLogDB{ 187 DB: db, 188 log: log, 189 eventUpdates: eventUpdates, 190 } 191 192 err = eventLogDB.upgradeDB() 193 if err != nil { 194 return nil, err 195 } 196 197 go func() { 198 eventLogDB.listenForStoreEvents(ctx) 199 db.Close() 200 }() 201 202 return eventLogDB, nil 203 } 204 205 // calcFinalStateBasedOnEventDiff calculated an updated final balance state 206 // based on the difference between an existing event and its updated version. 207 func calcFinalStateBasedOnEventDiff(runBucket, eventsBucket *bbolt.Bucket, eventKey []byte, newEvent *MarketMakingEvent) (*BalanceState, error) { 208 finalStateB := runBucket.Get(finalStateKey) 209 if finalStateB == nil { 210 return nil, fmt.Errorf("no final state found") 211 } 212 213 finalState, err := decodeFinalState(finalStateB) 214 if err != nil { 215 return nil, err 216 } 217 218 originalEventB := eventsBucket.Get(eventKey) 219 if originalEventB == nil { 220 return nil, fmt.Errorf("no original event found") 221 } 222 223 originalEvent, err := decodeMarketMakingEvent(originalEventB) 224 if err != nil { 225 return nil, err 226 } 227 228 applyDiff := func(curr uint64, diff int64) uint64 { 229 if diff > 0 { 230 return curr + uint64(diff) 231 } 232 233 if curr < uint64(-diff) { 234 return 0 235 } 236 237 return curr + uint64(diff) 238 } 239 240 balanceEffectDiff := newEvent.BalanceEffects.sub(originalEvent.BalanceEffects) 241 for assetID, diff := range balanceEffectDiff.settled { 242 finalState.Balances[assetID].Available = applyDiff(finalState.Balances[assetID].Available, diff) 243 } 244 for assetID, diff := range balanceEffectDiff.pending { 245 finalState.Balances[assetID].Pending = applyDiff(finalState.Balances[assetID].Pending, diff) 246 } 247 for assetID, diff := range balanceEffectDiff.locked { 248 finalState.Balances[assetID].Locked = applyDiff(finalState.Balances[assetID].Locked, diff) 249 } 250 for assetID, diff := range balanceEffectDiff.reserved { 251 finalState.Balances[assetID].Reserved = applyDiff(finalState.Balances[assetID].Reserved, diff) 252 } 253 254 return finalState, nil 255 } 256 257 func (db *boltEventLogDB) upgradeDB() error { 258 return db.Update(func(tx *bbolt.Tx) error { 259 botRuns := tx.Bucket(botRunsBucket) 260 versionB := botRuns.Get(versionKey) 261 262 var version uint32 263 if versionB != nil { 264 version = encode.BytesToUint32(versionB) 265 } 266 267 if version < balanceStateDBVersion { 268 err := botRuns.ForEachBucket(func(k []byte) error { 269 err := botRuns.DeleteBucket(k) 270 if err != nil { 271 return err 272 } 273 return nil 274 }) 275 if err != nil { 276 return err 277 } 278 } 279 280 return botRuns.Put(versionKey, encode.Uint32Bytes(balanceStateDBVersion)) 281 }) 282 } 283 284 // updateEvent is called for each event that is popped off the updateEvent. If 285 // the event already exists, it is updated. If it does not exist, it is added. 286 // The stats for the run are also updated based on the event. 287 func (db *boltEventLogDB) updateEvent(update *eventUpdate) { 288 if err := db.Update(func(tx *bbolt.Tx) error { 289 botRuns := tx.Bucket(botRunsBucket) 290 runBucket := botRuns.Bucket(update.runKey) 291 if runBucket == nil { 292 return fmt.Errorf("nil run bucket for key %x", update.runKey) 293 } 294 295 eventsBkt, err := runBucket.CreateBucketIfNotExists(eventsBucket) 296 if err != nil { 297 return err 298 } 299 eventKey := encode.Uint64Bytes(update.e.ID) 300 eventJSON, err := json.Marshal(update.e) 301 if err != nil { 302 return err 303 } 304 eventB := versionedBytes(0).AddData(eventJSON) 305 306 bs := update.bs 307 if bs == nil { 308 bs, err = calcFinalStateBasedOnEventDiff(runBucket, eventsBkt, eventKey, update.e) 309 if err != nil { 310 return err 311 } 312 } 313 314 if err := eventsBkt.Put(eventKey, eventB); err != nil { 315 return err 316 } 317 318 if update.e.UpdateConfig != nil { 319 if err := db.storeCfgUpdate(runBucket, update.e.UpdateConfig, update.e.TimeStamp); err != nil { 320 return err 321 } 322 } 323 324 // Update the final state. 325 bsJSON, err := json.Marshal(bs) 326 if err != nil { 327 return err 328 } 329 return runBucket.Put(finalStateKey, versionedBytes(0).AddData(bsJSON)) 330 }); err != nil { 331 db.log.Errorf("error storing event: %v", err) 332 } 333 } 334 335 // listedForStoreEvents listens on the updateEvent channel and updates the 336 // db one at a time. 337 func (db *boltEventLogDB) listenForStoreEvents(ctx context.Context) { 338 for { 339 select { 340 case e := <-db.eventUpdates: 341 db.updateEvent(e) 342 case <-ctx.Done(): 343 for len(db.eventUpdates) > 0 { 344 db.updateEvent(<-db.eventUpdates) 345 } 346 return 347 } 348 } 349 } 350 351 func versionedBytes(v byte) encode.BuildyBytes { 352 return encode.BuildyBytes{v} 353 } 354 355 // runKey is the key for a run bucket. 356 func runKey(startTime int64, mkt *MarketWithHost) []byte { 357 return versionedBytes(0). 358 AddData(encode.Uint64Bytes(uint64(startTime))). 359 AddData(encode.Uint32Bytes(mkt.BaseID)). 360 AddData(encode.Uint32Bytes(mkt.QuoteID)). 361 AddData([]byte(mkt.Host)) 362 } 363 364 func parseRunKey(key []byte) (startTime int64, mkt *MarketWithHost, err error) { 365 ver, pushes, err := encode.DecodeBlob(key) 366 if err != nil { 367 return 0, nil, err 368 } 369 if ver != 0 { 370 return 0, nil, fmt.Errorf("unknown version %d", ver) 371 } 372 if len(pushes) != 4 { 373 return 0, nil, fmt.Errorf("expected 4 pushes, got %d", len(pushes)) 374 } 375 376 startTime = int64(binary.BigEndian.Uint64(pushes[0])) 377 mkt = &MarketWithHost{ 378 BaseID: binary.BigEndian.Uint32(pushes[1]), 379 QuoteID: binary.BigEndian.Uint32(pushes[2]), 380 Host: string(pushes[3]), 381 } 382 return 383 } 384 385 func (db *boltEventLogDB) Close() error { 386 close(db.eventUpdates) 387 return db.DB.Close() 388 } 389 390 func (db *boltEventLogDB) storeCfgUpdate(runBucket *bbolt.Bucket, newCfg *BotConfig, timestamp int64) error { 391 cfgsBucket, err := runBucket.CreateBucketIfNotExists(cfgsBucket) 392 if err != nil { 393 return err 394 } 395 cfgB, err := json.Marshal(newCfg) 396 if err != nil { 397 return err 398 } 399 return cfgsBucket.Put(encode.Uint64Bytes(uint64(timestamp)), versionedBytes(0).AddData(cfgB)) 400 } 401 402 func (db *boltEventLogDB) cfgUpdates(runBucket *bbolt.Bucket) ([]*CfgUpdate, error) { 403 cfgsBucket := runBucket.Bucket(cfgsBucket) 404 if cfgsBucket == nil { 405 return nil, nil 406 } 407 408 cfgs := make([]*CfgUpdate, 0, cfgsBucket.Stats().KeyN) 409 cursor := cfgsBucket.Cursor() 410 for k, v := cursor.First(); k != nil; k, v = cursor.Next() { 411 timestamp := int64(binary.BigEndian.Uint64(k)) 412 cfg := new(BotConfig) 413 ver, pushes, err := encode.DecodeBlob(v) 414 if err != nil { 415 return nil, err 416 } 417 if ver != 0 { 418 return nil, fmt.Errorf("unknown version %d", ver) 419 } 420 if len(pushes) != 1 { 421 return nil, fmt.Errorf("expected 1 push for cfg, got %d", len(pushes)) 422 } 423 err = json.Unmarshal(pushes[0], cfg) 424 if err != nil { 425 return nil, err 426 } 427 cfgs = append(cfgs, &CfgUpdate{ 428 Timestamp: timestamp, 429 Cfg: cfg, 430 }) 431 } 432 433 return cfgs, nil 434 } 435 436 // storeNewRun stores a new run in the database. It should be called whenever 437 // a new run is started. Events cannot be stored for a run before this is 438 // called. 439 func (db *boltEventLogDB) storeNewRun(startTime int64, mkt *MarketWithHost, cfg *BotConfig, initialState *BalanceState) error { 440 return db.Update(func(tx *bbolt.Tx) error { 441 botRuns, err := tx.CreateBucketIfNotExists(botRunsBucket) 442 if err != nil { 443 return err 444 } 445 446 runBucket, err := botRuns.CreateBucketIfNotExists(runKey(startTime, mkt)) 447 if err != nil { 448 return err 449 } 450 451 runBucket.Put(startTimeKey, encode.Uint64Bytes(uint64(startTime))) 452 453 if err := db.storeCfgUpdate(runBucket, cfg, startTime); err != nil { 454 return err 455 } 456 457 initialBals := make(map[uint32]uint64) 458 for assetID, bal := range initialState.Balances { 459 initialBals[assetID] = bal.Available 460 } 461 initialBalsB, err := json.Marshal(initialBals) 462 if err != nil { 463 return err 464 } 465 runBucket.Put(initialBalsKey, versionedBytes(0).AddData(initialBalsB)) 466 467 fsB, err := json.Marshal(initialState) 468 if err != nil { 469 return err 470 } 471 runBucket.Put(finalStateKey, versionedBytes(0).AddData(fsB)) 472 473 return nil 474 }) 475 } 476 477 // runs returns a list of runs in the database. If n == 0, all of the runs will 478 // be returned. If refStartTime and refMkt are not nil, the runs including and 479 // before the run with the start time and market will be returned. 480 func (db *boltEventLogDB) runs(n uint64, refStartTime *uint64, refMkt *MarketWithHost) ([]*MarketMakingRun, error) { 481 if refStartTime == nil != (refMkt == nil) { 482 return nil, fmt.Errorf("both or neither refStartTime and refMkt must be nil") 483 } 484 485 var runs []*MarketMakingRun 486 err := db.View(func(tx *bbolt.Tx) error { 487 botRuns := tx.Bucket(botRunsBucket) 488 489 runs = make([]*MarketMakingRun, 0, botRuns.Stats().BucketN) 490 cursor := botRuns.Cursor() 491 492 var k []byte 493 if refStartTime == nil { 494 k, _ = cursor.Last() 495 } else { 496 k, _ = cursor.Seek(runKey(int64(*refStartTime), refMkt)) 497 } 498 499 for ; k != nil; k, _ = cursor.Prev() { 500 if bytes.Equal(k, versionKey) { 501 continue 502 } 503 startTime, mkt, err := parseRunKey(k) 504 if err != nil { 505 return err 506 } 507 508 runBucket := botRuns.Bucket(k) 509 if runBucket == nil { 510 return fmt.Errorf("nil run bucket for key %x", k) 511 } 512 513 runs = append(runs, &MarketMakingRun{ 514 StartTime: startTime, 515 Market: mkt, 516 }) 517 518 if n > 0 && uint64(len(runs)) >= n { 519 break 520 } 521 } 522 523 return nil 524 }) 525 if err != nil { 526 return nil, err 527 } 528 529 return runs, nil 530 } 531 532 func decodeFinalState(finalStateB []byte) (*BalanceState, error) { 533 finalState := new(BalanceState) 534 ver, pushes, err := encode.DecodeBlob(finalStateB) 535 if err != nil { 536 return nil, err 537 } 538 if ver != 0 { 539 return nil, fmt.Errorf("unknown final state version %d", ver) 540 } 541 if len(pushes) != 1 { 542 return nil, fmt.Errorf("expected 1 push for final state, got %d", len(pushes)) 543 } 544 err = json.Unmarshal(pushes[0], finalState) 545 if err != nil { 546 return nil, err 547 } 548 return finalState, nil 549 } 550 551 // runOverview returns overview information about a run, not including the 552 // events that took place. 553 func (db *boltEventLogDB) runOverview(startTime int64, mkt *MarketWithHost) (*MarketMakingRunOverview, error) { 554 var overview *MarketMakingRunOverview 555 return overview, db.View(func(tx *bbolt.Tx) error { 556 botRuns := tx.Bucket(botRunsBucket) 557 key := runKey(startTime, mkt) 558 runBucket := botRuns.Bucket(key) 559 if runBucket == nil { 560 return fmt.Errorf("nil run bucket for key %x", key) 561 } 562 563 initialBalsB := runBucket.Get(initialBalsKey) 564 initialBals := make(map[uint32]uint64) 565 ver, pushes, err := encode.DecodeBlob(initialBalsB) 566 if err != nil { 567 return err 568 } 569 if ver != 0 { 570 return fmt.Errorf("unknown initial bals version %d", ver) 571 } 572 if len(pushes) != 1 { 573 return fmt.Errorf("expected 1 push for initial bals, got %d", len(pushes)) 574 } 575 err = json.Unmarshal(pushes[0], &initialBals) 576 if err != nil { 577 return err 578 } 579 580 finalStateB := runBucket.Get(finalStateKey) 581 if finalStateB == nil { 582 return fmt.Errorf("no final state found") 583 } 584 585 finalState, err := decodeFinalState(finalStateB) 586 if err != nil { 587 return err 588 } 589 590 cfgs, err := db.cfgUpdates(runBucket) 591 if err != nil { 592 return err 593 } 594 595 endTimeB := runBucket.Get(endTimeKey) 596 var endTime *int64 597 if len(endTimeB) == 8 { 598 endTime = new(int64) 599 *endTime = int64(binary.BigEndian.Uint64(endTimeB)) 600 } 601 602 finalBals := make(map[uint32]uint64) 603 for assetID, bal := range finalState.Balances { 604 finalBals[assetID] = bal.Available + bal.Pending + bal.Locked + bal.Reserved 605 } 606 607 overview = &MarketMakingRunOverview{ 608 EndTime: endTime, 609 Cfgs: cfgs, 610 InitialBalances: initialBals, 611 ProfitLoss: newProfitLoss(initialBals, finalBals, finalState.InventoryMods, finalState.FiatRates), 612 FinalState: finalState, 613 } 614 615 return nil 616 }) 617 } 618 619 // endRun stores the time that a market making run was ended. 620 func (db *boltEventLogDB) endRun(startTime int64, mkt *MarketWithHost, endTime int64) error { 621 return db.Update(func(tx *bbolt.Tx) error { 622 botRuns := tx.Bucket(botRunsBucket) 623 key := runKey(startTime, mkt) 624 runBucket := botRuns.Bucket(key) 625 if runBucket == nil { 626 return fmt.Errorf("nil run bucket for key %x", key) 627 } 628 629 return runBucket.Put(endTimeKey, encode.Uint64Bytes(uint64(endTime))) 630 }) 631 } 632 633 // storeEvent stores/updates a market making event. BalanceState can be nil if 634 // the run has ended and the final state should be updated based on the 635 // difference between the event and the previous version of the event. 636 func (db *boltEventLogDB) storeEvent(startTime int64, mkt *MarketWithHost, e *MarketMakingEvent, bs *BalanceState) { 637 db.eventUpdates <- &eventUpdate{ 638 runKey: runKey(startTime, mkt), 639 e: e, 640 bs: bs, 641 } 642 } 643 644 func decodeMarketMakingEvent(eventB []byte) (*MarketMakingEvent, error) { 645 e := new(MarketMakingEvent) 646 ver, pushes, err := encode.DecodeBlob(eventB) 647 if err != nil { 648 return nil, err 649 } 650 if ver != 0 { 651 return nil, fmt.Errorf("unknown version %d", ver) 652 } 653 if len(pushes) != 1 { 654 return nil, fmt.Errorf("expected 1 push for event, got %d", len(pushes)) 655 } 656 err = json.Unmarshal(pushes[0], e) 657 if err != nil { 658 return nil, err 659 } 660 return e, nil 661 } 662 663 // runEvents returns the events that took place during a run. If n == 0, all of the 664 // events will be returned. If refID is not nil, the events including and after the 665 // event with the ID will be returned. If pendingOnly is true, only pending events 666 // will be returned. 667 func (db *boltEventLogDB) runEvents(startTime int64, mkt *MarketWithHost, n uint64, refID *uint64, pendingOnly bool, filter *RunLogFilters) ([]*MarketMakingEvent, error) { 668 events := make([]*MarketMakingEvent, 0, 32) 669 670 return events, db.View(func(tx *bbolt.Tx) error { 671 botRuns := tx.Bucket(botRunsBucket) 672 key := runKey(startTime, mkt) 673 runBucket := botRuns.Bucket(key) 674 if runBucket == nil { 675 return fmt.Errorf("nil run bucket for key %x", key) 676 } 677 678 // If a run has ended, and we have queried for all pending events and 679 // nothing was found, we store this fact to avoid having to search for 680 // pending events again. 681 if pendingOnly { 682 noPending := runBucket.Get(noPendingKey) 683 if noPending != nil { 684 return nil 685 } 686 } 687 688 eventsBkt := runBucket.Bucket(eventsBucket) 689 if eventsBkt == nil { 690 return nil 691 } 692 693 cursor := eventsBkt.Cursor() 694 var k, v []byte 695 if refID == nil { 696 k, v = cursor.Last() 697 } else { 698 k, v = cursor.Seek(encode.Uint64Bytes(*refID)) 699 } 700 701 for ; k != nil; k, v = cursor.Prev() { 702 e, err := decodeMarketMakingEvent(v) 703 if err != nil { 704 return err 705 } 706 707 if pendingOnly && !e.Pending { 708 continue 709 } 710 711 if filter != nil && !filter.filter(e) { 712 continue 713 } 714 715 events = append(events, e) 716 if n > 0 && uint64(len(events)) >= n { 717 break 718 } 719 } 720 721 // If there are no pending events, and the run as ended, store this 722 // information to avoid unnecessary iteration in the future. 723 if pendingOnly && len(events) == 0 && refID == nil && n == 0 { 724 endTime := runBucket.Get(endTimeKey) 725 if endTime != nil { 726 runBucket.Put(noPendingKey, []byte{1}) 727 } 728 } 729 730 return nil 731 }) 732 }