Bittrex: Enable ws orderbook sync recovery (resolves #746) (#747)

* [FIX] Enable ws orderbook sync recovery by:

- Testing if books have been cleared
- Assigning options when loading snapshot

* orderbooks: remove setlastupdate method and on select depth method that updates linked list, this reduced lock contention across code base and fixes buffer bug on applying buffered updates

* WS - Introduce signaling for the need to fetch the orderbook

* Address nits

* Update error messages to include exchange name

Co-authored-by: shazbert <oharareid.ryan@gmail.com>
This commit is contained in:
TaltaM
2021-08-18 03:43:46 +02:00
committed by GitHub
parent 08df015a57
commit 4ba2c710b5
7 changed files with 166 additions and 94 deletions

View File

@@ -284,9 +284,10 @@ type orderbookManager struct {
}
type update struct {
buffer chan *OrderbookUpdateMessage
fetchingBook bool
initialSync bool
buffer chan *OrderbookUpdateMessage
fetchingBook bool
initialSync bool
needsFetchingBook bool
}
// job defines a synchonisation job that tells a go routine to fetch an

View File

@@ -93,7 +93,7 @@ func (b *Bittrex) UpdateLocalOBBuffer(update *OrderbookUpdateMessage) (bool, err
err = b.applyBufferUpdate(currencyPair)
if err != nil {
b.flushAndCleanup(currencyPair)
log.Errorf(log.WebsocketMgr, "%s websocket UpdateLocalOBBuffer: Could not apply buffer update\n", b.Name)
}
return false, err
@@ -136,23 +136,40 @@ func (b *Bittrex) SeedLocalCacheWithOrderBook(p currency.Pair, sequence int64, o
// applyBufferUpdate applies the buffer to the orderbook or initiates a new
// orderbook sync by the REST protocol which is off handed to go routine.
func (b *Bittrex) applyBufferUpdate(pair currency.Pair) error {
fetching, err := b.obm.checkIsFetchingBook(pair)
fetching, needsFetching, err := b.obm.handleFetchingBook(pair)
if err != nil {
return err
}
if fetching {
return nil
}
recent, err := b.Websocket.Orderbook.GetOrderbook(pair, asset.Spot)
if err != nil || (recent.Asks == nil && recent.Bids == nil) {
if needsFetching {
if b.Verbose {
log.Debugf(log.WebsocketMgr, "Orderbook: Fetching via REST\n")
log.Debugf(log.WebsocketMgr, "%s Orderbook: Fetching via REST\n", b.Name)
}
return b.obm.fetchBookViaREST(pair)
}
recent, err := b.Websocket.Orderbook.GetOrderbook(pair, asset.Spot)
if err != nil {
log.Errorf(
log.WebsocketMgr,
"%s error fetching recent orderbook when applying updates: %s\n",
b.Name,
err)
}
return b.obm.checkAndProcessUpdate(b.ProcessUpdateOB, pair, recent)
if recent != nil {
err = b.obm.checkAndProcessUpdate(b.ProcessUpdateOB, pair, recent)
if err != nil {
log.Errorf(
log.WebsocketMgr,
"%s error processing update - initiating new orderbook sync via REST: %s\n",
b.Name,
err)
b.obm.setNeedsFetchingBook(pair)
}
}
return nil
}
// SynchroniseWebsocketOrderbook synchronises full orderbook for currency pair
@@ -192,12 +209,7 @@ func (b *Bittrex) processJob(p currency.Pair) error {
// Immediately apply the buffer updates so we don't wait for a
// new update to initiate this.
err = b.applyBufferUpdate(p)
if err != nil {
b.flushAndCleanup(p)
return err
}
return nil
return b.applyBufferUpdate(p)
}
// flushAndCleanup flushes orderbook and clean local cache
@@ -239,9 +251,10 @@ func (o *orderbookManager) stageWsUpdate(u *OrderbookUpdateMessage, pair currenc
state = &update{
// 100ms update assuming we might have up to a 10 second delay.
// There could be a potential 100 updates for the currency.
buffer: make(chan *OrderbookUpdateMessage, maxWSUpdateBuffer),
fetchingBook: false,
initialSync: true,
buffer: make(chan *OrderbookUpdateMessage, maxWSUpdateBuffer),
fetchingBook: false,
initialSync: true,
needsFetchingBook: true,
}
m2[a] = state
}
@@ -258,21 +271,6 @@ func (o *orderbookManager) stageWsUpdate(u *OrderbookUpdateMessage, pair currenc
}
}
// checkIsFetchingBook checks status if the book is currently being via the REST
// protocol.
func (o *orderbookManager) checkIsFetchingBook(pair currency.Pair) (bool, error) {
o.Lock()
defer o.Unlock()
state, ok := o.state[pair.Base][pair.Quote][asset.Spot]
if !ok {
return false,
fmt.Errorf("check is fetching book cannot match currency pair %s asset type %s",
pair,
asset.Spot)
}
return state.fetchingBook, nil
}
// stopFetchingBook completes the book fetching.
func (o *orderbookManager) stopFetchingBook(pair currency.Pair) error {
o.Lock()
@@ -292,6 +290,64 @@ func (o *orderbookManager) stopFetchingBook(pair currency.Pair) error {
return nil
}
// stopNeedsFetchingBook completes the book fetching initiation.
func (o *orderbookManager) stopNeedsFetchingBook(pair currency.Pair) error {
o.Lock()
defer o.Unlock()
state, ok := o.state[pair.Base][pair.Quote][asset.Spot]
if !ok {
return fmt.Errorf("could not match pair %s and asset type %s in hash table",
pair,
asset.Spot)
}
if !state.needsFetchingBook {
return fmt.Errorf("needs fetching book already set to false for %s %s",
pair,
asset.Spot)
}
state.needsFetchingBook = false
return nil
}
// setNeedsFetchingBook completes the book fetching initiation.
func (o *orderbookManager) setNeedsFetchingBook(pair currency.Pair) error {
o.Lock()
defer o.Unlock()
state, ok := o.state[pair.Base][pair.Quote][asset.Spot]
if !ok {
return fmt.Errorf("could not match pair %s and asset type %s in hash table",
pair,
asset.Spot)
}
state.needsFetchingBook = true
return nil
}
// handleFetchingBook checks if a full book is being fetched or needs to be
// fetched
func (o *orderbookManager) handleFetchingBook(pair currency.Pair) (fetching, needsFetching bool, err error) {
o.Lock()
defer o.Unlock()
state, ok := o.state[pair.Base][pair.Quote][asset.Spot]
if !ok {
return false, false,
fmt.Errorf("check is fetching book cannot match currency pair %s asset type %s",
pair,
asset.Spot)
}
if state.fetchingBook {
return true, false, nil
}
if state.needsFetchingBook {
state.needsFetchingBook = false
state.fetchingBook = true
return false, true, nil
}
return false, false, nil
}
// completeInitialSync sets if an asset type has completed its initial sync
func (o *orderbookManager) completeInitialSync(pair currency.Pair) error {
o.Lock()
@@ -437,5 +493,6 @@ bufferEmpty:
// disable rest orderbook synchronisation
_ = o.stopFetchingBook(pair)
_ = o.completeInitialSync(pair)
_ = o.stopNeedsFetchingBook(pair)
return nil
}

View File

@@ -94,8 +94,11 @@ func (d *Depth) TotalAskAmounts() (liquidity, value float64) {
}
// LoadSnapshot flushes the bids and asks with a snapshot
func (d *Depth) LoadSnapshot(bids, asks []Item) {
func (d *Depth) LoadSnapshot(bids, asks []Item, lastUpdateID int64, lastUpdated time.Time, updateByREST bool) {
d.m.Lock()
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
d.restSnapshot = updateByREST
d.bids.load(bids, d.stack)
d.asks.load(asks, d.stack)
d.alert()
@@ -105,6 +108,8 @@ func (d *Depth) LoadSnapshot(bids, asks []Item) {
// Flush flushes the bid and ask depths
func (d *Depth) Flush() {
d.m.Lock()
d.lastUpdateID = 0
d.lastUpdated = time.Time{}
d.bids.load(nil, d.stack)
d.asks.load(nil, d.stack)
d.alert()
@@ -113,11 +118,13 @@ func (d *Depth) Flush() {
// UpdateBidAskByPrice updates the bid and ask spread by supplied updates, this
// will trim total length of depth level to a specified supplied number
func (d *Depth) UpdateBidAskByPrice(bidUpdts, askUpdts Items, maxDepth int) {
func (d *Depth) UpdateBidAskByPrice(bidUpdts, askUpdts Items, maxDepth int, lastUpdateID int64, lastUpdated time.Time) {
if len(bidUpdts) == 0 && len(askUpdts) == 0 {
return
}
d.m.Lock()
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
tn := getNow()
if len(bidUpdts) != 0 {
d.bids.updateInsertByPrice(bidUpdts, d.stack, maxDepth, tn)
@@ -130,7 +137,7 @@ func (d *Depth) UpdateBidAskByPrice(bidUpdts, askUpdts Items, maxDepth int) {
}
// UpdateBidAskByID amends details by ID
func (d *Depth) UpdateBidAskByID(bidUpdts, askUpdts Items) error {
func (d *Depth) UpdateBidAskByID(bidUpdts, askUpdts Items, lastUpdateID int64, lastUpdated time.Time) error {
if len(bidUpdts) == 0 && len(askUpdts) == 0 {
return nil
}
@@ -148,12 +155,14 @@ func (d *Depth) UpdateBidAskByID(bidUpdts, askUpdts Items) error {
return err
}
}
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
d.alert()
return nil
}
// DeleteBidAskByID deletes a price level by ID
func (d *Depth) DeleteBidAskByID(bidUpdts, askUpdts Items, bypassErr bool) error {
func (d *Depth) DeleteBidAskByID(bidUpdts, askUpdts Items, bypassErr bool, lastUpdateID int64, lastUpdated time.Time) error {
if len(bidUpdts) == 0 && len(askUpdts) == 0 {
return nil
}
@@ -171,12 +180,14 @@ func (d *Depth) DeleteBidAskByID(bidUpdts, askUpdts Items, bypassErr bool) error
return err
}
}
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
d.alert()
return nil
}
// InsertBidAskByID inserts new updates
func (d *Depth) InsertBidAskByID(bidUpdts, askUpdts Items) error {
func (d *Depth) InsertBidAskByID(bidUpdts, askUpdts Items, lastUpdateID int64, lastUpdated time.Time) error {
if len(bidUpdts) == 0 && len(askUpdts) == 0 {
return nil
}
@@ -194,12 +205,14 @@ func (d *Depth) InsertBidAskByID(bidUpdts, askUpdts Items) error {
return err
}
}
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
d.alert()
return nil
}
// UpdateInsertByID updates or inserts by ID at current price level.
func (d *Depth) UpdateInsertByID(bidUpdts, askUpdts Items) error {
func (d *Depth) UpdateInsertByID(bidUpdts, askUpdts Items, lastUpdateID int64, lastUpdated time.Time) error {
if len(bidUpdts) == 0 && len(askUpdts) == 0 {
return nil
}
@@ -218,6 +231,8 @@ func (d *Depth) UpdateInsertByID(bidUpdts, askUpdts Items) error {
}
}
d.alert()
d.lastUpdateID = lastUpdateID
d.lastUpdated = lastUpdated
return nil
}
@@ -239,15 +254,6 @@ func (d *Depth) AssignOptions(b *Base) {
d.m.Unlock()
}
// SetLastUpdate sets details of last update information
func (d *Depth) SetLastUpdate(lastUpdate time.Time, lastUpdateID int64, updateByREST bool) {
d.m.Lock()
d.lastUpdated = lastUpdate
d.lastUpdateID = lastUpdateID
d.restSnapshot = updateByREST
d.m.Unlock()
}
// GetName returns name of exchange
func (d *Depth) GetName() string {
d.m.Lock()

View File

@@ -121,7 +121,7 @@ func TestTotalAmounts(t *testing.T) {
func TestLoadSnapshot(t *testing.T) {
d := newDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}})
d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false)
if d.Retrieve().Asks[0].Price != 1337 || d.Retrieve().Bids[0].Price != 1337 {
t.Fatal("not set")
}
@@ -129,12 +129,12 @@ func TestLoadSnapshot(t *testing.T) {
func TestFlush(t *testing.T) {
d := newDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}})
d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false)
d.Flush()
if len(d.Retrieve().Asks) != 0 || len(d.Retrieve().Bids) != 0 {
t.Fatal("not flushed")
}
d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}})
d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false)
d.Flush()
if len(d.Retrieve().Asks) != 0 || len(d.Retrieve().Bids) != 0 {
t.Fatal("not flushed")
@@ -143,12 +143,12 @@ func TestFlush(t *testing.T) {
func TestUpdateBidAskByPrice(t *testing.T) {
d := newDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}})
d.UpdateBidAskByPrice(Items{{Price: 1337, Amount: 2, ID: 1}}, Items{{Price: 1337, Amount: 2, ID: 2}}, 0)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)
d.UpdateBidAskByPrice(Items{{Price: 1337, Amount: 2, ID: 1}}, Items{{Price: 1337, Amount: 2, ID: 2}}, 0, 0, time.Time{})
if d.Retrieve().Asks[0].Amount != 2 || d.Retrieve().Bids[0].Amount != 2 {
t.Fatal("orderbook amounts not updated correctly")
}
d.UpdateBidAskByPrice(Items{{Price: 1337, Amount: 0, ID: 1}}, Items{{Price: 1337, Amount: 0, ID: 2}}, 0)
d.UpdateBidAskByPrice(Items{{Price: 1337, Amount: 0, ID: 1}}, Items{{Price: 1337, Amount: 0, ID: 2}}, 0, 0, time.Time{})
if d.GetAskLength() != 0 || d.GetBidLength() != 0 {
t.Fatal("orderbook amounts not updated correctly")
}
@@ -156,8 +156,8 @@ func TestUpdateBidAskByPrice(t *testing.T) {
func TestDeleteBidAskByID(t *testing.T) {
d := newDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}})
err := d.DeleteBidAskByID(Items{{Price: 1337, Amount: 2, ID: 1}}, Items{{Price: 1337, Amount: 2, ID: 2}}, false)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)
err := d.DeleteBidAskByID(Items{{Price: 1337, Amount: 2, ID: 1}}, Items{{Price: 1337, Amount: 2, ID: 2}}, false, 0, time.Time{})
if err != nil {
t.Fatal(err)
}
@@ -165,17 +165,17 @@ func TestDeleteBidAskByID(t *testing.T) {
t.Fatal("items not deleted")
}
err = d.DeleteBidAskByID(Items{{Price: 1337, Amount: 2, ID: 1}}, nil, false)
err = d.DeleteBidAskByID(Items{{Price: 1337, Amount: 2, ID: 1}}, nil, false, 0, time.Time{})
if !errors.Is(err, errIDCannotBeMatched) {
t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err)
}
err = d.DeleteBidAskByID(nil, Items{{Price: 1337, Amount: 2, ID: 2}}, false)
err = d.DeleteBidAskByID(nil, Items{{Price: 1337, Amount: 2, ID: 2}}, false, 0, time.Time{})
if !errors.Is(err, errIDCannotBeMatched) {
t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err)
}
err = d.DeleteBidAskByID(nil, Items{{Price: 1337, Amount: 2, ID: 2}}, true)
err = d.DeleteBidAskByID(nil, Items{{Price: 1337, Amount: 2, ID: 2}}, true, 0, time.Time{})
if !errors.Is(err, nil) {
t.Fatalf("error expected %v received %v", nil, err)
}
@@ -183,8 +183,8 @@ func TestDeleteBidAskByID(t *testing.T) {
func TestUpdateBidAskByID(t *testing.T) {
d := newDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}})
err := d.UpdateBidAskByID(Items{{Price: 1337, Amount: 2, ID: 1}}, Items{{Price: 1337, Amount: 2, ID: 2}})
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)
err := d.UpdateBidAskByID(Items{{Price: 1337, Amount: 2, ID: 1}}, Items{{Price: 1337, Amount: 2, ID: 2}}, 0, time.Time{})
if err != nil {
t.Fatal(err)
}
@@ -193,12 +193,12 @@ func TestUpdateBidAskByID(t *testing.T) {
}
// random unmatching IDs
err = d.UpdateBidAskByID(Items{{Price: 1337, Amount: 2, ID: 666}}, nil)
err = d.UpdateBidAskByID(Items{{Price: 1337, Amount: 2, ID: 666}}, nil, 0, time.Time{})
if !errors.Is(err, errIDCannotBeMatched) {
t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err)
}
err = d.UpdateBidAskByID(nil, Items{{Price: 1337, Amount: 2, ID: 69}})
err = d.UpdateBidAskByID(nil, Items{{Price: 1337, Amount: 2, ID: 69}}, 0, time.Time{})
if !errors.Is(err, errIDCannotBeMatched) {
t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err)
}
@@ -206,8 +206,8 @@ func TestUpdateBidAskByID(t *testing.T) {
func TestInsertBidAskByID(t *testing.T) {
d := newDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}})
err := d.InsertBidAskByID(Items{{Price: 1338, Amount: 2, ID: 3}}, Items{{Price: 1336, Amount: 2, ID: 4}})
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)
err := d.InsertBidAskByID(Items{{Price: 1338, Amount: 2, ID: 3}}, Items{{Price: 1336, Amount: 2, ID: 4}}, 0, time.Time{})
if err != nil {
t.Fatal(err)
}
@@ -218,19 +218,19 @@ func TestInsertBidAskByID(t *testing.T) {
func TestUpdateInsertByID(t *testing.T) {
d := newDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}})
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)
err := d.UpdateInsertByID(Items{{Price: 1338, Amount: 0, ID: 3}}, Items{{Price: 1336, Amount: 2, ID: 4}})
err := d.UpdateInsertByID(Items{{Price: 1338, Amount: 0, ID: 3}}, Items{{Price: 1336, Amount: 2, ID: 4}}, 0, time.Time{})
if !errors.Is(err, errAmountCannotBeLessOrEqualToZero) {
t.Fatalf("expected: %v but received: %v", errAmountCannotBeLessOrEqualToZero, err)
}
err = d.UpdateInsertByID(Items{{Price: 1338, Amount: 2, ID: 3}}, Items{{Price: 1336, Amount: 0, ID: 4}})
err = d.UpdateInsertByID(Items{{Price: 1338, Amount: 2, ID: 3}}, Items{{Price: 1336, Amount: 0, ID: 4}}, 0, time.Time{})
if !errors.Is(err, errAmountCannotBeLessOrEqualToZero) {
t.Fatalf("expected: %v but received: %v", errAmountCannotBeLessOrEqualToZero, err)
}
err = d.UpdateInsertByID(Items{{Price: 1338, Amount: 2, ID: 3}}, Items{{Price: 1336, Amount: 2, ID: 4}})
err = d.UpdateInsertByID(Items{{Price: 1338, Amount: 2, ID: 3}}, Items{{Price: 1336, Amount: 2, ID: 4}}, 0, time.Time{})
if err != nil {
t.Fatal(err)
}
@@ -271,17 +271,6 @@ func TestAssignOptions(t *testing.T) {
}
}
func TestSetLastUpdate(t *testing.T) {
d := Depth{}
tn := time.Now()
d.SetLastUpdate(tn, 1337, true)
if d.lastUpdated != tn ||
d.lastUpdateID != 1337 ||
!d.restSnapshot {
t.Fatal("failed to set correctly")
}
}
func TestGetName(t *testing.T) {
d := Depth{}
d.exchange = "test"

View File

@@ -79,8 +79,7 @@ func (s *Service) Update(b *Base) error {
book.AssignOptions(b)
m3[b.Pair.Quote.Item] = book
}
book.SetLastUpdate(b.LastUpdated, b.LastUpdateID, true)
book.LoadSnapshot(b.Bids, b.Asks)
book.LoadSnapshot(b.Bids, b.Asks, b.LastUpdateID, b.LastUpdated, true)
s.Unlock()
return s.Mux.Publish([]uuid.UUID{m1.ID}, book.Retrieve())
}

View File

@@ -102,9 +102,6 @@ func (w *Orderbook) Update(u *Update) error {
u.Asset)
}
// Apply new update information
book.ob.SetLastUpdate(u.UpdateTime, u.UpdateID, false)
if w.bufferEnabled {
processed, err := w.processBufferUpdate(book, u)
if err != nil {
@@ -193,7 +190,11 @@ func (w *Orderbook) processObUpdate(o *orderbookHolder, u *Update) error {
// updateByPrice ammends amount if match occurs by price, deletes if amount is
// zero or less and inserts if not found.
func (o *orderbookHolder) updateByPrice(updts *Update) {
o.ob.UpdateBidAskByPrice(updts.Bids, updts.Asks, updts.MaxDepth)
o.ob.UpdateBidAskByPrice(updts.Bids,
updts.Asks,
updts.MaxDepth,
updts.UpdateID,
updts.UpdateTime)
}
// updateByIDAndAction will receive an action to execute against the orderbook
@@ -201,15 +202,28 @@ func (o *orderbookHolder) updateByPrice(updts *Update) {
func (o *orderbookHolder) updateByIDAndAction(updts *Update) error {
switch updts.Action {
case Amend:
return o.ob.UpdateBidAskByID(updts.Bids, updts.Asks)
return o.ob.UpdateBidAskByID(updts.Bids,
updts.Asks,
updts.UpdateID,
updts.UpdateTime)
case Delete:
// edge case for Bitfinex as their streaming endpoint duplicates deletes
bypassErr := o.ob.GetName() == "Bitfinex" && o.ob.IsFundingRate()
return o.ob.DeleteBidAskByID(updts.Bids, updts.Asks, bypassErr)
return o.ob.DeleteBidAskByID(updts.Bids,
updts.Asks,
bypassErr,
updts.UpdateID,
updts.UpdateTime)
case Insert:
return o.ob.InsertBidAskByID(updts.Bids, updts.Asks)
return o.ob.InsertBidAskByID(updts.Bids,
updts.Asks,
updts.UpdateID,
updts.UpdateTime)
case UpdateInsert:
return o.ob.UpdateInsertByID(updts.Bids, updts.Asks)
return o.ob.UpdateInsertByID(updts.Bids,
updts.Asks,
updts.UpdateID,
updts.UpdateTime)
default:
return fmt.Errorf("invalid action [%s]", updts.Action)
}
@@ -253,7 +267,13 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error {
return err
}
holder.ob.LoadSnapshot(book.Bids, book.Asks)
holder.ob.LoadSnapshot(
book.Bids,
book.Asks,
book.LastUpdateID,
book.LastUpdated,
false,
)
if holder.ob.VerifyOrderbook { // This is used here so as to not retrieve
// book if verification is off.

View File

@@ -802,7 +802,7 @@ func TestUpdateByIDAndAction(t *testing.T) {
t.Fatal(err)
}
book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...))
book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Time{}, true)
err = book.Retrieve().Verify()
if err != nil {
@@ -924,7 +924,7 @@ func TestUpdateByIDAndAction(t *testing.T) {
t.Fatal("did not adjust ask item placement and details")
}
book.LoadSnapshot(append(bids[:0:0], bids...), append(bids[:0:0], bids...)) // nolint:gocritic
book.LoadSnapshot(append(bids[:0:0], bids...), append(bids[:0:0], bids...), 0, time.Time{}, true) // nolint:gocritic
// Delete - not found
err = holder.updateByIDAndAction(&Update{