Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

admin: Add Accounts. #441

Merged
merged 2 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions server/account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package account
import (
"database/sql/driver"
"encoding/hex"
"encoding/json"
"fmt"

"decred.org/dcrdex/server/account/pki"
Expand Down Expand Up @@ -31,6 +32,12 @@ func (aid AccountID) String() string {
return hex.EncodeToString(aid[:])
}

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

// Value implements the sql/driver.Valuer interface.
func (aid AccountID) Value() (driver.Value, error) {
return aid[:], nil // []byte
Expand Down
10 changes: 10 additions & 0 deletions server/admin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,13 @@ func (s *Server) apiSuspend(w http.ResponseWriter, r *http.Request) {
SuspendTime: APITime{suspEpoch.End},
})
}

// apiAccounts is the handler for the '/accounts' API request.
func (s *Server) apiAccounts(w http.ResponseWriter, _ *http.Request) {
accts, err := s.core.Accounts()
if err != nil {
http.Error(w, fmt.Sprintf("failed to retrieve accounts: %v", err), http.StatusInternalServerError)
JoeGruffins marked this conversation as resolved.
Show resolved Hide resolved
return
}
writeJSON(w, accts)
}
3 changes: 3 additions & 0 deletions 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/db"
"decred.org/dcrdex/server/market"
"github.com/decred/slog"
"github.com/go-chi/chi"
Expand All @@ -38,6 +39,7 @@ var (

// SvrCore is satisfied by server/dex.DEX.
type SvrCore interface {
Accounts() ([]*db.Account, error)
ConfigMsg() json.RawMessage
MarketRunning(mktName string) (found, running bool)
MarketStatus(mktName string) *market.Status
Expand Down Expand Up @@ -118,6 +120,7 @@ func NewServer(cfg *SrvConfig) (*Server, error) {
r.Use(middleware.AllowContentType("application/json"))
r.Get("/ping", s.apiPing)
r.Get("/config", s.apiConfig)
r.Get("/accounts", s.apiAccounts)

r.Get("/markets", s.apiMarkets)
r.Route("/market/{"+marketNameKey+"}", func(rm chi.Router) {
Expand Down
100 changes: 99 additions & 1 deletion server/admin/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"context"
"crypto/elliptic"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
Expand All @@ -20,7 +22,10 @@ import (
"testing"
"time"

"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/encode"
"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/db"
"decred.org/dcrdex/server/market"
"github.com/decred/dcrd/certgen"
"github.com/decred/slog"
Expand All @@ -41,7 +46,9 @@ type TMarket struct {
}

type TCore struct {
markets map[string]*TMarket
markets map[string]*TMarket
accounts []*db.Account
accountsErr error
}

func (c *TCore) ConfigMsg() json.RawMessage { return nil }
Expand Down Expand Up @@ -130,6 +137,8 @@ func (w *tResponseWriter) WriteHeader(statusCode int) {
w.code = statusCode
}

func (c *TCore) Accounts() ([]*db.Account, error) { return c.accounts, c.accountsErr }

// genCertPair generates a key/cert pair to the paths provided.
func genCertPair(certFile, keyFile string) error {
log.Infof("Generating TLS certificates...")
Expand Down Expand Up @@ -696,3 +705,92 @@ func TestAuthMiddleware(t *testing.T) {
wantAuthError(test.name, test.wantErr)
}
}

func TestAccounts(t *testing.T) {
core := &TCore{
accounts: []*db.Account{},
}
srv := &Server{
core: core,
}

mux := chi.NewRouter()
mux.Get("/accounts", srv.apiAccounts)

// No accounts.
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "https://localhost/accounts", 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 != "[]\n" {
t.Errorf("incorrect response body: %q", respBody)
}

accountIDSlice, err := hex.DecodeString("0a9912205b2cbab0c25c2de30bda9074de0ae23b065489a99199bad763f102cc")
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.
acct := &db.Account{
AccountID: accountID,
Pubkey: dex.Bytes(pubkey),
FeeAddress: "DsdQFmH3azyoGKJHt2ArJNxi35LCEgMqi8k",
FeeCoin: dex.Bytes(feeCoin),
BrokenRule: account.Rule(byte(255)),
}
core.accounts = append(core.accounts, acct)

w = httptest.NewRecorder()
r, _ = http.NewRequest("GET", "https://localhost/accounts", 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)
}

// core.Accounts error
core.accountsErr = errors.New("error")

w = httptest.NewRecorder()
r, _ = http.NewRequest("GET", "https://localhost/accounts", 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)
}
}
24 changes: 24 additions & 0 deletions server/db/driver/pg/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"

"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/db"
"decred.org/dcrdex/server/db/driver/pg/internal"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil/v2"
Expand Down Expand Up @@ -43,6 +44,29 @@ func (a *Archiver) Account(aid account.AccountID) (*account.Account, bool, bool)
return acct, isPaid, isOpen
}

// Accounts returns data for all accounts.
func (a *Archiver) Accounts() ([]*db.Account, error) {
stmt := fmt.Sprintf(internal.SelectAllAccounts, a.tables.accounts)
rows, err := a.db.Query(stmt)
if err != nil {
return nil, err
}
defer rows.Close()
var accts []*db.Account
for rows.Next() {
a := new(db.Account)
err = rows.Scan(&a.AccountID, &a.Pubkey, &a.FeeAddress, &a.FeeCoin, &a.BrokenRule)
if err != nil {
return nil, err
}
accts = append(accts, a)
}
if err = rows.Err(); err != nil {
return nil, err
}
return accts, 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
12 changes: 12 additions & 0 deletions server/db/driver/pg/accounts_online_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ func TestAccounts(t *testing.T) {
t.Fatalf("newly paid account marked as closed")
}

accts, err := archie.Accounts()
if err != nil {
t.Fatalf("error getting accounts: %v", err)
}
if accts[0].AccountID.String() != "0a9912205b2cbab0c25c2de30bda9074de0ae23b065489a99199bad763f102cc" ||
accts[0].Pubkey.String() != "0204988a498d5d19514b217e872b4dbd1cf071d365c4879e64ed5919881c97eb19" ||
accts[0].FeeAddress != "DsdQFmH3azyoGKJHt2ArJNxi35LCEgMqi8k" ||
accts[0].FeeCoin.String() != "6e515ff861f2016fd0da2f3eccdf8290c03a9d116bfba2f6729e648bdc6e5aed00000005" ||
byte(accts[0].BrokenRule) != byte(0) {
t.Fatal("accounts has unexpected data")
}

// Close the account for failure to complete a swap.
archie.CloseAccount(tAcctID, account.FailureToAct)
_, _, open = archie.Account(tAcctID)
Expand Down
3 changes: 3 additions & 0 deletions server/db/driver/pg/internal/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const (
FROM %s
WHERE account_id = $1;`

// SelectAllAccounts retrieves all accounts.
SelectAllAccounts = `SELECT * FROM %s;`

// 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 @@ -179,6 +179,9 @@ type AccountArchiver interface {
// PayAccount sets the registration fee payment transaction details for the
// account, completing the registration process.
PayAccount(account.AccountID, []byte) error

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

// MatchData represents an order pair match, but with just the order IDs instead
Expand Down
18 changes: 18 additions & 0 deletions server/db/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// This code is available on the terms of the project LICENSE.md file,
// also available online at https://blueoakcouncil.org/license/1.0.0.

package db

import (
"decred.org/dcrdex/dex"
"decred.org/dcrdex/server/account"
)

// Account holds data returned by Accounts.
type Account struct {
AccountID account.AccountID `json:"accountid"`
Pubkey dex.Bytes `json:"pubkey"`
FeeAddress string `json:"feeaddress"`
FeeCoin dex.Bytes `json:"feecoin"`
BrokenRule account.Rule `json:"brokenrule"`
}
5 changes: 5 additions & 0 deletions server/dex/dex.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,3 +547,8 @@ func (dm *DEX) SuspendMarket(name string, tSusp time.Time, persistBooks bool) *m

// TODO: resume by relaunching the market subsystems (Run)
// Resume / ResumeMarket

// Accounts returns data for all accounts.
func (dm *DEX) Accounts() ([]*db.Account, error) {
return dm.storage.Accounts()
}
1 change: 1 addition & 0 deletions server/market/market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ func (ta *TArchivist) Account(account.AccountID) (acct *account.Account, paid, o
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 randomOrderID() order.OrderID {
Expand Down