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 }