Skip to content

Commit

Permalink
admin: Add account endpoint.
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeGruffins committed Jun 9, 2020
1 parent 95d8c3f commit 82b4c60
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 7 deletions.
20 changes: 20 additions & 0 deletions server/admin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package admin

import (
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -12,6 +13,7 @@ import (
"time"

"decred.org/dcrdex/dex/encode"
"decred.org/dcrdex/server/account"
"github.com/go-chi/chi"
)

Expand Down Expand Up @@ -156,3 +158,21 @@ func (s *Server) apiAccounts(w http.ResponseWriter, _ *http.Request) {
}
writeJSON(w, accts)
}

// apiAccountInfo is the handler for the '/account/{account id}' API request.
func (s *Server) apiAccountInfo(w http.ResponseWriter, r *http.Request) {
acct := strings.ToLower(chi.URLParam(r, accountNameKey))
acctIDSlice, err := hex.DecodeString(acct)
if err != nil {
http.Error(w, fmt.Sprintf("could not decode accout id: %v", err), http.StatusBadRequest)
return
}
var acctID account.AccountID
copy(acctID[:], acctIDSlice)
acctInfo, err := s.core.AccountInfo(acctID)
if err != nil {
http.Error(w, fmt.Sprintf("failed to retrieve account: %v", err), http.StatusInternalServerError)
return
}
writeJSON(w, acctInfo)
}
8 changes: 7 additions & 1 deletion server/admin/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"sync"
"time"

"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/db"
"decred.org/dcrdex/server/market"
"github.com/decred/slog"
Expand All @@ -30,7 +31,8 @@ const (
// is closed.
rpcTimeoutSeconds = 10

marketNameKey = "market"
marketNameKey = "market"
accountNameKey = "account"
)

var (
Expand All @@ -40,6 +42,7 @@ var (
// SvrCore is satisfied by server/dex.DEX.
type SvrCore interface {
Accounts() ([]*db.Account, error)
AccountInfo(account.AccountID) (*db.Account, error)
ConfigMsg() json.RawMessage
MarketRunning(mktName string) (found, running bool)
MarketStatus(mktName string) *market.Status
Expand Down Expand Up @@ -121,6 +124,9 @@ func NewServer(cfg *SrvConfig) (*Server, error) {
r.Get("/ping", s.apiPing)
r.Get("/config", s.apiConfig)
r.Get("/accounts", s.apiAccounts)
r.Route("/account/{"+accountNameKey+"}", func(rm chi.Router) {
rm.Get("/", s.apiAccountInfo)
})

r.Get("/markets", s.apiMarkets)
r.Route("/market/{"+marketNameKey+"}", func(rm chi.Router) {
Expand Down
104 changes: 104 additions & 0 deletions server/admin/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type TCore struct {
markets map[string]*TMarket
accounts []*db.Account
accountsErr error
account *db.Account
accountErr error
}

func (c *TCore) ConfigMsg() json.RawMessage { return nil }
Expand Down Expand Up @@ -138,6 +140,9 @@ func (w *tResponseWriter) WriteHeader(statusCode int) {
}

func (c *TCore) Accounts() ([]*db.Account, error) { return c.accounts, c.accountsErr }
func (c *TCore) AccountInfo(_ account.AccountID) (*db.Account, error) {
return c.account, c.accountErr
}

// genCertPair generates a key/cert pair to the paths provided.
func genCertPair(certFile, keyFile string) error {
Expand Down Expand Up @@ -794,3 +799,102 @@ func TestAccounts(t *testing.T) {
t.Fatalf("apiAccounts returned code %d, expected %d", w.Code, http.StatusInternalServerError)
}
}

func TestAccountInfo(t *testing.T) {
core := new(TCore)
srv := &Server{
core: core,
}

acctIDStr := "0a9912205b2cbab0c25c2de30bda9074de0ae23b065489a99199bad763f102cc"

mux := chi.NewRouter()
mux.Route("/account/{"+accountNameKey+"}", func(rm chi.Router) {
rm.Get("/", srv.apiAccountInfo)
})

// No account.
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "https://localhost/account/"+acctIDStr, nil)
r.RemoteAddr = "localhost"

mux.ServeHTTP(w, r)

if w.Code != http.StatusOK {
t.Fatalf("apiAccounts returned code %d, expected %d", w.Code, http.StatusOK)
}
respBody := w.Body.String()
if respBody != "null\n" {
t.Errorf("incorrect response body: %q", respBody)
}

accountIDSlice, err := hex.DecodeString(acctIDStr)
if err != nil {
t.Fatal(err)
}
var accountID account.AccountID
copy(accountID[:], accountIDSlice)
pubkey, err := hex.DecodeString("0204988a498d5d19514b217e872b4dbd1cf071d365c4879e64ed5919881c97eb19")
if err != nil {
t.Fatal(err)
}
feeCoin, err := hex.DecodeString("6e515ff861f2016fd0da2f3eccdf8290c03a9d116bfba2f6729e648bdc6e5aed00000005")
if err != nil {
t.Fatal(err)
}

// An account.
core.account = &db.Account{
AccountID: accountID,
Pubkey: dex.Bytes(pubkey),
FeeAddress: "DsdQFmH3azyoGKJHt2ArJNxi35LCEgMqi8k",
FeeCoin: dex.Bytes(feeCoin),
BrokenRule: account.Rule(byte(255)),
}

w = httptest.NewRecorder()
r, _ = http.NewRequest("GET", "https://localhost/account/"+acctIDStr, nil)
r.RemoteAddr = "localhost"

mux.ServeHTTP(w, r)

if w.Code != http.StatusOK {
t.Fatalf("apiAccounts returned code %d, expected %d", w.Code, http.StatusOK)
}

exp := `{
"accountid": "0a9912205b2cbab0c25c2de30bda9074de0ae23b065489a99199bad763f102cc",
"pubkey": "0204988a498d5d19514b217e872b4dbd1cf071d365c4879e64ed5919881c97eb19",
"feeaddress": "DsdQFmH3azyoGKJHt2ArJNxi35LCEgMqi8k",
"feecoin": "6e515ff861f2016fd0da2f3eccdf8290c03a9d116bfba2f6729e648bdc6e5aed00000005",
"brokenrule": 255
}
`
if exp != w.Body.String() {
t.Errorf("unexpected response %q, wanted %q", w.Body.String(), exp)
}

// acct id is not hex
w = httptest.NewRecorder()
r, _ = http.NewRequest("GET", "https://localhost/account/nothex", nil)
r.RemoteAddr = "localhost"

mux.ServeHTTP(w, r)

if w.Code != http.StatusBadRequest {
t.Fatalf("apiAccounts returned code %d, expected %d", w.Code, http.StatusBadRequest)
}

// core.Account error
core.accountErr = errors.New("error")

w = httptest.NewRecorder()
r, _ = http.NewRequest("GET", "https://localhost/account/"+acctIDStr, nil)
r.RemoteAddr = "localhost"

mux.ServeHTTP(w, r)

if w.Code != http.StatusInternalServerError {
t.Fatalf("apiAccounts returned code %d, expected %d", w.Code, http.StatusInternalServerError)
}
}
25 changes: 24 additions & 1 deletion server/db/driver/pg/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"

"decred.org/dcrdex/dex"
"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/db"
"decred.org/dcrdex/server/db/driver/pg/internal"
Expand Down Expand Up @@ -53,12 +54,18 @@ func (a *Archiver) Accounts() ([]*db.Account, error) {
}
defer rows.Close()
var accts []*db.Account
var accountID, pubkey, feeCoin []byte
var brokenRule byte
for rows.Next() {
a := new(db.Account)
err = rows.Scan(&a.AccountID, &a.Pubkey, &a.FeeAddress, &a.FeeCoin, &a.BrokenRule)
err = rows.Scan(&accountID, &pubkey, &a.FeeAddress, &feeCoin, &brokenRule)
if err != nil {
return nil, err
}
copy(a.AccountID[:], accountID)
a.Pubkey = dex.Bytes(pubkey)
a.FeeCoin = dex.Bytes(feeCoin)
a.BrokenRule = account.Rule(brokenRule)
accts = append(accts, a)
}
if err = rows.Err(); err != nil {
Expand All @@ -67,6 +74,22 @@ func (a *Archiver) Accounts() ([]*db.Account, error) {
return accts, nil
}

// AccountInfo returns data for an account.
func (a *Archiver) AccountInfo(aid account.AccountID) (*db.Account, error) {
stmt := fmt.Sprintf(internal.SelectAccountInfo, a.tables.accounts)
acct := new(db.Account)
var accountID, pubkey, feeCoin []byte
var brokenRule byte
if err := a.db.QueryRow(stmt, aid).Scan(&accountID, &pubkey, &acct.FeeAddress, &feeCoin, &brokenRule); err != nil {
return nil, err
}
copy(acct.AccountID[:], accountID)
acct.Pubkey = dex.Bytes(pubkey)
acct.FeeCoin = dex.Bytes(feeCoin)
acct.BrokenRule = account.Rule(brokenRule)
return acct, nil
}

// CreateAccount creates an entry for a new account in the accounts table. A
// DCR registration fee address is created and returned.
func (a *Archiver) CreateAccount(acct *account.Account) (string, error) {
Expand Down
9 changes: 9 additions & 0 deletions server/db/driver/pg/accounts_online_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package pg

import (
"encoding/hex"
"reflect"
"testing"

"decred.org/dcrdex/server/account"
Expand Down Expand Up @@ -89,6 +90,14 @@ func TestAccounts(t *testing.T) {
t.Fatal("accounts has unexpected data")
}

anAcct, err := archie.AccountInfo(accts[0].AccountID)
if err != nil {
t.Fatalf("error getting account info: %v", err)
}
if !reflect.DeepEqual(accts[0], anAcct) {
t.Fatal("error getting account info: actual does not equal expected")
}

// Close the account for failure to complete a swap.
archie.CloseAccount(tAcctID, account.FailureToAct)
_, _, open = archie.Account(tAcctID)
Expand Down
4 changes: 4 additions & 0 deletions server/db/driver/pg/internal/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ const (
// SelectAllAccounts retrieves all accounts.
SelectAllAccounts = `SELECT * FROM %s;`

// SelectAccountInfo retrieves all fields for an account.
SelectAccountInfo = `SELECT * FROM %s
WHERE account_id = $1;`

// CreateAccount creates an entry for a new account.
CreateAccount = `INSERT INTO %s (account_id, pubkey, fee_address)
VALUES ($1, $2, $3);`
Expand Down
3 changes: 3 additions & 0 deletions server/db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ type AccountArchiver interface {

// Accounts returns data for all accounts.
Accounts() ([]*Account, error)

// AccountInfo returns data for an account.
AccountInfo(account.AccountID) (*Account, error)
}

// MatchData represents an order pair match, but with just the order IDs instead
Expand Down
6 changes: 6 additions & 0 deletions server/dex/dex.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/encode"
"decred.org/dcrdex/dex/msgjson"
"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/asset"
dcrasset "decred.org/dcrdex/server/asset/dcr"
"decred.org/dcrdex/server/auth"
Expand Down Expand Up @@ -552,3 +553,8 @@ func (dm *DEX) SuspendMarket(name string, tSusp time.Time, persistBooks bool) *m
func (dm *DEX) Accounts() ([]*db.Account, error) {
return dm.storage.Accounts()
}

// AccountInfo returns data for an account.
func (dm *DEX) AccountInfo(aid account.AccountID) (*db.Account, error) {
return dm.storage.AccountInfo(aid)
}
11 changes: 6 additions & 5 deletions server/market/market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,12 @@ func (ta *TArchivist) CloseAccount(account.AccountID, account.Rule) {}
func (ta *TArchivist) Account(account.AccountID) (acct *account.Account, paid, open bool) {
return nil, false, false
}
func (ta *TArchivist) CreateAccount(*account.Account) (string, error) { return "", nil }
func (ta *TArchivist) AccountRegAddr(account.AccountID) (string, error) { return "", nil }
func (ta *TArchivist) PayAccount(account.AccountID, []byte) error { return nil }
func (ta *TArchivist) Accounts() ([]*db.Account, error) { return nil, nil }
func (ta *TArchivist) Close() error { return nil }
func (ta *TArchivist) CreateAccount(*account.Account) (string, error) { return "", nil }
func (ta *TArchivist) AccountRegAddr(account.AccountID) (string, error) { return "", nil }
func (ta *TArchivist) PayAccount(account.AccountID, []byte) error { return nil }
func (ta *TArchivist) Accounts() ([]*db.Account, error) { return nil, nil }
func (ta *TArchivist) AccountInfo(account.AccountID) (*db.Account, error) { return nil, nil }
func (ta *TArchivist) Close() error { return nil }

func randomOrderID() order.OrderID {
pk := randomBytes(order.OrderIDSize)
Expand Down

0 comments on commit 82b4c60

Please sign in to comment.