decred.org/dcrdex@v1.0.5/client/orderbook/epochqueue.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 orderbook 5 6 import ( 7 "bytes" 8 "fmt" 9 "sort" 10 "sync" 11 12 "decred.org/dcrdex/dex/msgjson" 13 "decred.org/dcrdex/dex/order" 14 "github.com/decred/dcrd/crypto/blake256" 15 ) 16 17 // epochOrder represents a compact EpochOrderNote. 18 type epochOrder struct { 19 Side uint8 20 Quantity uint64 21 Rate uint64 22 Commitment order.Commitment 23 epoch uint64 24 } 25 26 // EpochQueue represents a client epoch queue. 27 type EpochQueue struct { 28 mtx sync.RWMutex 29 orders map[order.OrderID]*epochOrder 30 } 31 32 // NewEpochQueue creates a client epoch queue. 33 func NewEpochQueue() *EpochQueue { 34 return &EpochQueue{ 35 orders: make(map[order.OrderID]*epochOrder), 36 } 37 } 38 39 // Enqueue appends the provided order note to the epoch queue. 40 func (eq *EpochQueue) Enqueue(note *msgjson.EpochOrderNote) error { 41 eq.mtx.Lock() 42 defer eq.mtx.Unlock() 43 44 if len(note.OrderID) != order.OrderIDSize { 45 return fmt.Errorf("expected order id length of %d, got %d", 46 order.OrderIDSize, len(note.OrderID)) 47 } 48 49 var oid order.OrderID 50 copy(oid[:], note.OrderID) 51 52 // Ensure the provided epoch order is not already queued. 53 _, ok := eq.orders[oid] 54 if ok { 55 return fmt.Errorf("%s is already queued", oid) 56 } 57 58 var commitment order.Commitment 59 copy(commitment[:], note.Commit) 60 61 order := &epochOrder{ 62 Commitment: commitment, 63 Quantity: note.Quantity, 64 Rate: note.Rate, 65 Side: note.Side, 66 epoch: note.Epoch, 67 } 68 69 eq.orders[oid] = order 70 71 return nil 72 } 73 74 // Size returns the number of entries in the epoch queue. 75 func (eq *EpochQueue) Size() int { 76 eq.mtx.RLock() 77 size := len(eq.orders) 78 eq.mtx.RUnlock() 79 return size 80 } 81 82 // Exists checks if the provided order id is in the queue. 83 func (eq *EpochQueue) Exists(oid order.OrderID) bool { 84 eq.mtx.RLock() 85 _, ok := eq.orders[oid] 86 eq.mtx.RUnlock() 87 return ok 88 } 89 90 // pimgMatch pairs matched preimage and epochOrder while generating the match 91 // proof. 92 type pimgMatch struct { 93 id order.OrderID 94 ord *epochOrder 95 pimg order.Preimage 96 } 97 98 // GenerateMatchProof calculates the sorting seed used in order matching 99 // as well as the commitment checksum from the provided epoch queue 100 // preimages and misses. 101 // 102 // The epoch queue needs to be reset if there are preimage mismatches or 103 // non-existent orders for preimage errors. 104 func (eq *EpochQueue) GenerateMatchProof(preimages []order.Preimage, misses []order.OrderID) (msgjson.Bytes, msgjson.Bytes, error) { 105 eq.mtx.Lock() 106 defer eq.mtx.Unlock() 107 108 if len(eq.orders) == 0 { 109 return nil, nil, fmt.Errorf("cannot generate match proof with an empty epoch queue") 110 } 111 112 // Get the commitments for all orders in the epoch queue. 113 commits := make([]*order.Commitment, 0, len(eq.orders)) 114 for _, ord := range eq.orders { 115 commits = append(commits, &ord.Commitment) 116 } 117 118 // Sort the commitments slice. 119 sort.Slice(commits, func(i, j int) bool { 120 return bytes.Compare(commits[i][:], commits[j][:]) < 0 121 }) 122 123 // Generate the commitment checksum. 124 comH := blake256.New() 125 for _, commit := range commits { 126 comH.Write(commit[:]) 127 } 128 csum := comH.Sum(nil) 129 130 // Remove all misses. 131 for _, oid := range misses { 132 delete(eq.orders, oid) 133 } 134 135 // Map the preimages received with their associated epoch order ids. 136 matches := make([]*pimgMatch, 0, len(preimages)) 137 outer: 138 for _, pimg := range preimages { 139 commitment := blake256.Sum256(pimg[:]) 140 for oid, ord := range eq.orders { 141 if ord.Commitment == commitment { 142 matches = append(matches, &pimgMatch{ 143 id: oid, 144 ord: ord, 145 pimg: pimg, 146 }) 147 delete(eq.orders, oid) 148 continue outer 149 } 150 } 151 return nil, nil, fmt.Errorf("no order match found for preimage %x", pimg) 152 } 153 154 sort.Slice(matches, func(i, j int) bool { 155 return bytes.Compare(matches[i].id[:], matches[j].id[:]) < 0 156 }) 157 158 for oid := range eq.orders { 159 fmt.Printf("WARNING! Order not accounted for in match proof: %v\n", oid) 160 // csum will not match, so just note what was omitted for debugging. 161 } 162 163 // Compute the hash of the concatenated preimages, sorted by order ID. 164 piH := blake256.New() 165 for _, match := range matches { 166 piH.Write(match.pimg[:]) 167 } 168 seed := piH.Sum(nil) 169 170 return seed, csum, nil 171 } 172 173 // Orders returns the epoch queue as a []*Order. 174 func (eq *EpochQueue) Orders() (orders []*Order) { 175 eq.mtx.Lock() 176 defer eq.mtx.Unlock() 177 for oid, ord := range eq.orders { 178 orders = append(orders, &Order{ 179 OrderID: oid, 180 Side: ord.Side, 181 Quantity: ord.Quantity, 182 Rate: ord.Rate, 183 Epoch: ord.epoch, 184 }) 185 } 186 return 187 }