decred.org/dcrdex@v1.0.5/server/auth/cancel.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 auth
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"sort"
    10  	"sync"
    11  
    12  	"decred.org/dcrdex/dex/order"
    13  )
    14  
    15  // freeCancelThreshold is the minimum number of epochs a user should wait before
    16  // placing a cancel order, if they want to avoid penalization. It is set to 2,
    17  // which means if a user places a cancel order in the same epoch as its limit
    18  // order, or the next epoch, the user will be penalized. This value is chosen
    19  // because it is the minimum value such that the order remains booked for at
    20  // least one full epoch and one full match cycle.
    21  const freeCancelThreshold = 2
    22  
    23  // oidStamped is a time-stamped order ID, with a field for target order ID if
    24  // the order is a cancel order.
    25  type oidStamped struct {
    26  	order.OrderID
    27  	time     int64
    28  	target   *order.OrderID
    29  	epochGap int32
    30  }
    31  
    32  // ordsByTimeThenID is used to sort an ord slice in ascending order by time and
    33  // then order ID. This puts the oldest order at the front and latest at the back
    34  // of the slice.
    35  type ordsByTimeThenID []*oidStamped
    36  
    37  func (o ordsByTimeThenID) Len() int {
    38  	return len(o)
    39  }
    40  
    41  func (o ordsByTimeThenID) Swap(i, j int) {
    42  	o[j], o[i] = o[i], o[j]
    43  }
    44  
    45  func (o ordsByTimeThenID) Less(i, j int) bool {
    46  	return less(o[i], o[j])
    47  }
    48  
    49  func less(oi, oj *oidStamped) bool {
    50  	if oi.time == oj.time {
    51  		cmp := bytes.Compare(oi.OrderID[:], oj.OrderID[:])
    52  		// The same order should not be in the slice more than once, but nothing
    53  		// will explode so just log it.
    54  		if cmp == 0 {
    55  			log.Errorf(fmt.Sprintf("slice contains more than one instance of order %v",
    56  				oi.OrderID))
    57  		}
    58  		return cmp < 0 // ascending (smaller order ID first)
    59  	}
    60  	return oi.time < oj.time // ascending (newest last in slice)
    61  }
    62  
    63  // latestOrders manages a list of the latest orders for a user. Its purpose is
    64  // to track cancellation frequency.
    65  type latestOrders struct {
    66  	mtx    sync.Mutex
    67  	cap    int16
    68  	orders []*oidStamped
    69  }
    70  
    71  func newLatestOrders(cap int16) *latestOrders {
    72  	return &latestOrders{
    73  		cap:    cap,
    74  		orders: make([]*oidStamped, 0, cap+1), // cap+1 since an old order is always popped after a new one is pushed
    75  	}
    76  }
    77  
    78  /*func (lo *latestOrders) addSimple(o *ord) {
    79  	lo.mtx.Lock()
    80  	defer lo.mtx.Unlock()
    81  
    82  	// push back, where the latest order goes
    83  	lo.orders = append(lo.orders, o)
    84  
    85  	// Should be few if any swaps. This is only to deal with the possibility of
    86  	// adding an order that is not the latest, and equal time stamps.
    87  	sort.Sort(ordsByTimeThenID(lo.orders))
    88  
    89  	// Pop one order if the slice was at capacity prior to pushing the new one.
    90  	for len(lo.orders) > int(lo.cap) {
    91  		// pop front, the oldest order
    92  		lo.orders[0] = nil // avoid memory leak
    93  		lo.orders = lo.orders[1:]
    94  	}
    95  }*/
    96  
    97  func (lo *latestOrders) add(o *oidStamped) {
    98  	lo.mtx.Lock()
    99  	defer lo.mtx.Unlock()
   100  
   101  	// Use sort.Search and insert it at the right spot.
   102  	n := len(lo.orders)
   103  	i := sort.Search(n, func(i int) bool {
   104  		return less(lo.orders[n-1-i], o)
   105  	})
   106  	if i == int(lo.cap) /* i == n && n == int(lo.cap) */ {
   107  		// The new one is the oldest/smallest, but already at capacity.
   108  		return
   109  	}
   110  	// Insert at proper location.
   111  	i = n - i // i-1 is first location that stays
   112  	lo.orders = append(lo.orders[:i], append([]*oidStamped{o}, lo.orders[i:]...)...)
   113  
   114  	// Pop one order if the slice was at capacity prior to pushing the new one.
   115  	if len(lo.orders) > int(lo.cap) {
   116  		// pop front, the oldest order
   117  		lo.orders[0] = nil // avoid memory leak
   118  		lo.orders = lo.orders[1:]
   119  	}
   120  }
   121  
   122  func (lo *latestOrders) counts() (total, cancels int) {
   123  	lo.mtx.Lock()
   124  	defer lo.mtx.Unlock()
   125  
   126  	total = len(lo.orders)
   127  	for _, o := range lo.orders {
   128  		if o.target != nil && o.epochGap >= 0 && o.epochGap < freeCancelThreshold {
   129  			cancels++
   130  		}
   131  	}
   132  
   133  	return
   134  }