decred.org/dcrdex@v1.0.5/dex/order/match.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 order 5 6 import ( 7 "database/sql/driver" 8 "encoding/binary" 9 "encoding/hex" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "time" 14 15 "decred.org/dcrdex/dex/calc" 16 "github.com/decred/dcrd/crypto/blake256" 17 ) 18 19 // MatchIDSize defines the length in bytes of an MatchID. 20 const MatchIDSize = blake256.Size 21 22 // MatchID is the unique identifier for each match. 23 type MatchID [MatchIDSize]byte 24 25 // MatchID implements fmt.Stringer. 26 func (id MatchID) String() string { 27 return hex.EncodeToString(id[:]) 28 } 29 30 // MarshalJSON satisfies the json.Marshaller interface, and will marshal the 31 // id to a hex string. 32 func (id MatchID) MarshalJSON() ([]byte, error) { 33 return json.Marshal(id.String()) 34 } 35 36 // Bytes returns the match ID as a []byte. 37 func (id MatchID) Bytes() []byte { 38 return id[:] 39 } 40 41 // Value implements the sql/driver.Valuer interface. 42 func (id MatchID) Value() (driver.Value, error) { 43 return id[:], nil // []byte 44 } 45 46 // Scan implements the sql.Scanner interface. 47 func (id *MatchID) Scan(src any) error { 48 idB, ok := src.([]byte) 49 if !ok { 50 return fmt.Errorf("cannot convert %T to OrderID", src) 51 } 52 copy(id[:], idB) 53 return nil 54 } 55 56 var zeroMatchID MatchID 57 58 // DecodeMatchID checks a string as being both hex and the right length and 59 // returns its bytes encoded as an order.MatchID. 60 func DecodeMatchID(matchIDStr string) (MatchID, error) { 61 var matchID MatchID 62 if len(matchIDStr) != MatchIDSize*2 { 63 return matchID, errors.New("match id has incorrect length") 64 } 65 if _, err := hex.Decode(matchID[:], []byte(matchIDStr)); err != nil { 66 return matchID, fmt.Errorf("could not decode match id: %w", err) 67 } 68 return matchID, nil 69 } 70 71 // MatchStatus represents the current negotiation step for a match. 72 type MatchStatus uint8 73 74 // The different states of order execution. 75 const ( 76 // NewlyMatched: DEX has sent match notifications, but the maker has not yet 77 // acted. 78 NewlyMatched MatchStatus = iota // 0 79 // MakerSwapCast: Maker has acknowledged their match notification and 80 // broadcast their swap notification. The DEX has validated the swap 81 // notification and sent the details to the taker. 82 MakerSwapCast // 1 83 // TakerSwapCast: Taker has acknowledged their match notification and 84 // broadcast their swap notification. The DEX has validated the swap 85 // notification and sent the details to the maker. 86 TakerSwapCast // 2 87 // MakerRedeemed: Maker has acknowledged their audit request and broadcast 88 // their redemption transaction. The DEX has validated the redemption and 89 // sent the details to the taker. 90 MakerRedeemed // 3 91 // MatchComplete: Taker has acknowledged their audit request and broadcast 92 // their redemption transaction. The DEX has validated the redemption and 93 // sent the details to the maker. 94 MatchComplete // 4 95 // MatchConfirmed is a status used only by the client that represents 96 // that the user's redemption transaction has been confirmed. 97 MatchConfirmed // 5 98 ) 99 100 // String satisfies fmt.Stringer. 101 func (status MatchStatus) String() string { 102 switch status { 103 case NewlyMatched: 104 return "NewlyMatched" 105 case MakerSwapCast: 106 return "MakerSwapCast" 107 case TakerSwapCast: 108 return "TakerSwapCast" 109 case MakerRedeemed: 110 return "MakerRedeemed" 111 case MatchComplete: 112 return "MatchComplete" 113 case MatchConfirmed: 114 return "MatchConfirmed" 115 } 116 return "MatchStatusUnknown" 117 } 118 119 // MatchSide is the client's side in a match. It will be one of Maker or Taker. 120 type MatchSide uint8 121 122 const ( 123 // Maker is the order that matches out of the epoch queue. 124 Maker MatchSide = iota 125 // Taker is the order from the order book. 126 Taker 127 ) 128 129 func (side MatchSide) String() string { 130 switch side { 131 case Maker: 132 return "Maker" 133 case Taker: 134 return "Taker" 135 } 136 return "UnknownMatchSide" 137 } 138 139 // Signatures holds the acknowledgement signatures required for swap 140 // negotiation. 141 type Signatures struct { 142 TakerMatch []byte 143 MakerMatch []byte 144 TakerAudit []byte 145 MakerAudit []byte 146 TakerRedeem []byte 147 MakerRedeem []byte 148 } 149 150 // EpochID contains the uniquely-identifying information for an epoch: index and 151 // duration. 152 type EpochID struct { 153 Idx uint64 154 Dur uint64 155 } 156 157 // End is the end time of the epoch. 158 func (e *EpochID) End() time.Time { 159 return time.UnixMilli(int64((e.Idx + 1) * e.Dur)) 160 } 161 162 // Match represents a match between two orders. 163 type Match struct { 164 Taker Order 165 Maker *LimitOrder 166 Quantity uint64 167 Rate uint64 168 169 // The following fields are not part of the serialization of Match. 170 FeeRateBase uint64 171 FeeRateQuote uint64 172 Epoch EpochID 173 Status MatchStatus 174 Sigs Signatures 175 cachedHash MatchID 176 } 177 178 // A UserMatch is similar to a Match, but contains less information about the 179 // counter-party, and it clarifies which side the user is on. This is the 180 // information that might be provided to the client when they are resyncing 181 // their matches after a reconnect. 182 type UserMatch struct { 183 OrderID OrderID 184 MatchID MatchID 185 Quantity uint64 186 Rate uint64 187 Address string 188 Status MatchStatus 189 Side MatchSide 190 FeeRateSwap uint64 191 // TODO: include Sell bool? 192 } 193 194 // String is the match ID string, implements fmt.Stringer. 195 func (m *UserMatch) String() string { 196 return m.MatchID.String() 197 } 198 199 // A constructor for a Match with Status = NewlyMatched. This is the preferred 200 // method of making a Match, since it pre-calculates and caches the match ID. 201 func newMatch(taker Order, maker *LimitOrder, qty, rate, feeRateBase, feeRateQuote uint64, epochID EpochID) *Match { 202 m := &Match{ 203 Taker: taker, 204 Maker: maker, 205 Quantity: qty, 206 Rate: rate, 207 Epoch: epochID, 208 FeeRateBase: feeRateBase, 209 FeeRateQuote: feeRateQuote, 210 } 211 // Pre-cache the ID. 212 m.ID() 213 return m 214 } 215 216 // ID computes the match ID and stores it for future calls. 217 // BLAKE256([maker order id] + [taker order id] + [match qty] + [match rate]) 218 func (match *Match) ID() MatchID { 219 if match.cachedHash != zeroMatchID { 220 return match.cachedHash 221 } 222 b := make([]byte, 0, 2*OrderIDSize+8+8) 223 b = appendOrderID(b, match.Taker) 224 b = appendOrderID(b, match.Maker) // this maker and taker may only be matched once 225 b = appendUint64Bytes(b, match.Quantity) 226 b = appendUint64Bytes(b, match.Rate) 227 match.cachedHash = blake256.Sum256(b) 228 return match.cachedHash 229 } 230 231 // MatchSet represents the result of matching a single Taker order from the 232 // epoch queue with one or more standing limit orders from the book, the Makers. 233 // The Amounts and Rates of each standing order paired are stored. The Rates 234 // slice is for convenience since each rate must be the same as the Maker's 235 // rate. However, a amount in Amounts may be less than the full quantity of the 236 // corresponding Maker order, indicating a partial fill of the Maker. The sum 237 // of the amounts, Total, is provided for convenience. 238 type MatchSet struct { 239 Epoch EpochID 240 Taker Order 241 Makers []*LimitOrder 242 Amounts []uint64 243 Rates []uint64 244 Total uint64 245 FeeRateBase uint64 246 FeeRateQuote uint64 247 } 248 249 // Matches converts the MatchSet to a []*Match. 250 func (set *MatchSet) Matches() []*Match { 251 matches := make([]*Match, 0, len(set.Makers)) 252 for i, maker := range set.Makers { 253 match := newMatch(set.Taker, maker, set.Amounts[i], set.Rates[i], set.FeeRateBase, set.FeeRateQuote, set.Epoch) 254 matches = append(matches, match) 255 } 256 return matches 257 } 258 259 // HighLowRates gets the highest and lowest rate from all matches. 260 func (set *MatchSet) HighLowRates() (high uint64, low uint64) { 261 for _, rate := range set.Rates { 262 if rate > high { 263 high = rate 264 } 265 if rate < low || low == 0 { 266 low = rate 267 } 268 } 269 return 270 } 271 272 // QuoteVolume is the matched quantity in terms of the quote asset. 273 func (set *MatchSet) QuoteVolume() (v uint64) { 274 for i := range set.Rates { 275 v += calc.BaseToQuote(set.Rates[i], set.Amounts[i]) 276 } 277 return 278 } 279 280 func appendUint64Bytes(b []byte, i uint64) []byte { 281 iBytes := make([]byte, 8) 282 binary.BigEndian.PutUint64(iBytes, i) 283 return append(b, iBytes...) 284 } 285 286 func appendOrderID(b []byte, order Order) []byte { 287 oid := order.ID() 288 return append(b, oid[:]...) 289 } 290 291 // MatchProof contains the key results of an epoch's order matching. 292 type MatchProof struct { 293 Epoch EpochID 294 Preimages []Preimage 295 Misses []Order 296 CSum []byte 297 Seed []byte 298 }