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  }