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  }