decred.org/dcrdex@v1.0.5/server/auth/latest.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 "sort" 9 "sync" 10 11 "decred.org/dcrdex/dex/order" 12 ) 13 14 type matchOutcome struct { 15 // sorting is done by time and match ID 16 time int64 17 mid order.MatchID 18 19 // match outcome and value 20 outcome Violation 21 base, quote uint32 // market 22 value uint64 23 } 24 25 func lessByTimeThenMID(ti, tj *matchOutcome) bool { 26 if ti.time == tj.time { 27 return bytes.Compare(ti.mid[:], tj.mid[:]) < 0 // ascending (smaller ID first) 28 } 29 return ti.time < tj.time // ascending (newest last in slice) 30 } 31 32 type latestMatchOutcomes struct { 33 mtx sync.Mutex 34 cap int16 35 outcomes []*matchOutcome 36 } 37 38 func newLatestMatchOutcomes(cap int16) *latestMatchOutcomes { 39 return &latestMatchOutcomes{ 40 cap: cap, 41 outcomes: make([]*matchOutcome, 0, cap+1), // cap+1 since an old item is popped *after* a new one is pushed 42 } 43 } 44 45 func (la *latestMatchOutcomes) add(mo *matchOutcome) { 46 la.mtx.Lock() 47 defer la.mtx.Unlock() 48 49 // Do a dumb search for the match ID, without regard to time, so we can't 50 // insert a match twice. 51 for _, oc := range la.outcomes { 52 if oc.mid == mo.mid { 53 log.Warnf("(*latestMatchOutcomes).add: Rejecting duplicate match ID: %v", mo.mid) 54 return 55 } 56 } 57 58 // Use sort.Search and insert it at the right spot. 59 n := len(la.outcomes) 60 i := sort.Search(n, func(i int) bool { 61 return lessByTimeThenMID(la.outcomes[n-1-i], mo) 62 }) 63 if i == int(la.cap) /* i == n && n == int(la.cap) */ { 64 // The new one is the oldest/smallest, but already at capacity. 65 return 66 } 67 // Insert at proper location. 68 i = n - i // i-1 is first location that stays 69 la.outcomes = append(la.outcomes[:i], append([]*matchOutcome{mo}, la.outcomes[i:]...)...) 70 71 // Pop one stamped if the slice was at capacity prior to pushing the new one. 72 if len(la.outcomes) > int(la.cap) { 73 // pop front, the oldest stamped 74 la.outcomes[0] = nil // avoid memory leak 75 la.outcomes = la.outcomes[1:] 76 } 77 } 78 79 func (la *latestMatchOutcomes) binViolations() map[Violation]int64 { 80 la.mtx.Lock() 81 defer la.mtx.Unlock() 82 83 bins := make(map[Violation]int64) 84 for _, mo := range la.outcomes { 85 bins[mo.outcome]++ 86 } 87 return bins 88 } 89 90 type preimageOutcome struct { 91 time int64 92 oid order.OrderID 93 miss bool 94 } 95 96 func lessByTimeThenOID(ti, tj *preimageOutcome) bool { 97 if ti.time == tj.time { 98 return bytes.Compare(ti.oid[:], tj.oid[:]) < 0 // ascending (smaller ID first) 99 } 100 return ti.time < tj.time // ascending (newest last in slice) 101 } 102 103 type latestPreimageOutcomes struct { 104 mtx sync.Mutex 105 cap int16 106 outcomes []*preimageOutcome 107 } 108 109 func newLatestPreimageOutcomes(cap int16) *latestPreimageOutcomes { 110 return &latestPreimageOutcomes{ 111 cap: cap, 112 outcomes: make([]*preimageOutcome, 0, cap+1), // cap+1 since an old item is popped *after* a new one is pushed 113 } 114 } 115 116 func (la *latestPreimageOutcomes) add(po *preimageOutcome) { 117 la.mtx.Lock() 118 defer la.mtx.Unlock() 119 120 // Do a dumb search for the order ID, without regard to time, so we can't 121 // insert an order twice. 122 for _, oc := range la.outcomes { 123 if oc.oid == po.oid { 124 log.Warnf("(*latestPreimageOutcomes).add: Rejecting duplicate order ID: %v", po.oid) 125 return 126 } 127 } 128 129 // Use sort.Search and insert it at the right spot. 130 n := len(la.outcomes) 131 i := sort.Search(n, func(i int) bool { 132 return lessByTimeThenOID(la.outcomes[n-1-i], po) 133 }) 134 if i == int(la.cap) /* i == n && n == int(la.cap) */ { 135 // The new one is the oldest/smallest, but already at capacity. 136 return 137 } 138 // Insert at proper location. 139 i = n - i // i-1 is first location that stays 140 la.outcomes = append(la.outcomes[:i], append([]*preimageOutcome{po}, la.outcomes[i:]...)...) 141 142 // Pop one stamped if the slice was at capacity prior to pushing the new one. 143 if len(la.outcomes) > int(la.cap) { 144 // pop front, the oldest stamped 145 la.outcomes[0] = nil // avoid memory leak 146 la.outcomes = la.outcomes[1:] 147 } 148 } 149 150 func (la *latestPreimageOutcomes) misses() (misses int32) { 151 la.mtx.Lock() 152 defer la.mtx.Unlock() 153 154 for _, th := range la.outcomes { 155 if th.miss { 156 misses++ 157 } 158 } 159 return 160 }