Skip to content

Commit

Permalink
admin: Add market admin endpoints
Browse files Browse the repository at this point in the history
Add orderbook, epochorders, and matches endpoints.
  • Loading branch information
JoeGruffins authored and chappjc committed Nov 18, 2020
1 parent 2a0607c commit 0ce3ec7
Show file tree
Hide file tree
Showing 17 changed files with 650 additions and 16 deletions.
7 changes: 7 additions & 0 deletions dex/order/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"database/sql/driver"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"time"
Expand All @@ -26,6 +27,12 @@ func (id MatchID) String() string {
return hex.EncodeToString(id[:])
}

// MarshalJSON satisfies the json.Marshaller interface, and will marshal the
// id to a hex string.
func (id MatchID) MarshalJSON() ([]byte, error) {
return json.Marshal(id.String())
}

// Bytes returns the match ID as a []byte.
func (id MatchID) Bytes() []byte {
return id[:]
Expand Down
7 changes: 7 additions & 0 deletions dex/order/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"database/sql/driver"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"sync"
"time"
Expand Down Expand Up @@ -50,6 +51,12 @@ func (oid OrderID) String() string {
return hex.EncodeToString(oid[:])
}

// MarshalJSON satisfies the json.Marshaller interface, and will marshal the
// id to a hex string.
func (oid OrderID) MarshalJSON() ([]byte, error) {
return json.Marshal(oid.String())
}

// Bytes returns the order ID as a []byte.
func (oid OrderID) Bytes() []byte {
return oid[:]
Expand Down
98 changes: 98 additions & 0 deletions server/admin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"decred.org/dcrdex/dex/msgjson"
"decred.org/dcrdex/dex/order"
"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/market"
"github.com/go-chi/chi"
)

Expand Down Expand Up @@ -107,6 +108,103 @@ func (s *Server) apiMarketInfo(w http.ResponseWriter, r *http.Request) {
writeJSON(w, mktStatus)
}

// apiMarketOrderBook is the handler for the '/market/{marketName}/orderbook'
// API request.
func (s *Server) apiMarketOrderBook(w http.ResponseWriter, r *http.Request) {
mkt := strings.ToLower(chi.URLParam(r, marketNameKey))
status := s.core.MarketStatus(mkt)
if status == nil {
http.Error(w, fmt.Sprintf("unknown market %q", mkt), http.StatusBadRequest)
return
}
orders, err := s.core.BookOrders(status.Base, status.Quote)
if err != nil {
http.Error(w, fmt.Sprintf("failed to obtain order book: %v", err), http.StatusInternalServerError)
return
}
msgBook := make([]*msgjson.BookOrderNote, 0, len(orders))
for _, o := range orders {
msgOrder, err := market.OrderToMsgOrder(o, mkt)
if err != nil {
log.Errorf("unable to encode order: %w", err)
continue
}
msgBook = append(msgBook, msgOrder)
}
// This is a msgjson.OrderBook without the seq field.
res := &struct {
MarketID string `json:"marketid"`
Epoch uint64 `json:"epoch"`
Orders []*msgjson.BookOrderNote `json:"orders"`
}{
MarketID: mkt,
Epoch: uint64(status.ActiveEpoch),
Orders: msgBook,
}
writeJSON(w, res)
}

// hander for route '/market/{marketName}/epochorders' API request.
func (s *Server) apiMarketEpochOrders(w http.ResponseWriter, r *http.Request) {
mkt := strings.ToLower(chi.URLParam(r, marketNameKey))
status := s.core.MarketStatus(mkt)
if status == nil {
http.Error(w, fmt.Sprintf("unknown market %q", mkt), http.StatusBadRequest)
return
}
orders, err := s.core.EpochOrders(status.Base, status.Quote)
if err != nil {
http.Error(w, fmt.Sprintf("failed to obtain epoch orders: %v", err), http.StatusInternalServerError)
return
}
msgBook := make([]*msgjson.BookOrderNote, 0, len(orders))
for _, o := range orders {
msgOrder, err := market.OrderToMsgOrder(o, mkt)
if err != nil {
log.Errorf("unable to encode order: %w", err)
continue
}
msgBook = append(msgBook, msgOrder)
}
// This is a msgjson.OrderBook without the seq field.
res := &struct {
MarketID string `json:"marketid"`
Epoch uint64 `json:"epoch"`
Orders []*msgjson.BookOrderNote `json:"orders"`
}{
MarketID: mkt,
Epoch: uint64(status.ActiveEpoch),
Orders: msgBook,
}
writeJSON(w, res)
}

// hander for route '/market/{marketName}/matches?includeinactive=BOOL' API
// request.
func (s *Server) apiMarketMatches(w http.ResponseWriter, r *http.Request) {
var includeInactive bool
if includeInactiveStr := r.URL.Query().Get(includeInactiveToken); includeInactiveStr != "" {
var err error
includeInactive, err = strconv.ParseBool(includeInactiveStr)
if err != nil {
http.Error(w, fmt.Sprintf("invalid include inactive boolean %q: %v", includeInactiveStr, err), http.StatusBadRequest)
return
}
}
mkt := strings.ToLower(chi.URLParam(r, marketNameKey))
status := s.core.MarketStatus(mkt)
if status == nil {
http.Error(w, fmt.Sprintf("unknown market %q", mkt), http.StatusBadRequest)
return
}
matches, err := s.core.MarketMatches(status.Base, status.Quote, includeInactive)
if err != nil {
http.Error(w, fmt.Sprintf("failed to obtain match data: %v", err), http.StatusInternalServerError)
return
}
writeJSON(w, matches)
}

// hander for route '/market/{marketName}/resume?t=UNIXMS'
func (s *Server) apiResume(w http.ResponseWriter, r *http.Request) {
// Ensure the market exists and is not running.
Expand Down
17 changes: 12 additions & 5 deletions server/admin/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ const (
// is closed.
rpcTimeoutSeconds = 10

marketNameKey = "market"
accountIDKey = "account"
matchIDKey = "match"
ruleToken = "rule"
timeoutToken = "timeout"
marketNameKey = "market"
accountIDKey = "account"
matchIDKey = "match"
ruleToken = "rule"
timeoutToken = "timeout"
includeInactiveToken = "includeinactive"
)

var (
Expand All @@ -60,6 +61,9 @@ type SvrCore interface {
Penalize(aid account.AccountID, rule account.Rule, details string) error
Unban(aid account.AccountID) error
ForgiveMatchFail(aid account.AccountID, mid order.MatchID) (forgiven, unbanned bool, err error)
BookOrders(base, quote uint32) (orders []*order.LimitOrder, err error)
EpochOrders(base, quote uint32) (orders []order.Order, err error)
MarketMatches(base, quote uint32, includeInactive bool) ([]*db.MatchData, error)
}

// Server is a multi-client https server.
Expand Down Expand Up @@ -147,6 +151,9 @@ func NewServer(cfg *SrvConfig) (*Server, error) {
r.Get("/markets", s.apiMarkets)
r.Route("/market/{"+marketNameKey+"}", func(rm chi.Router) {
rm.Get("/", s.apiMarketInfo)
rm.Get("/orderbook", s.apiMarketOrderBook)
rm.Get("/epochorders", s.apiMarketEpochOrders)
rm.Get("/matches", s.apiMarketMatches)
rm.Get("/suspend", s.apiSuspend)
rm.Get("/resume", s.apiResume)
})
Expand Down
Loading

0 comments on commit 0ce3ec7

Please sign in to comment.