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