Expose auth validator functionality for wrapper (#416)

* expose auth validator functionality for wrapper

* Add REST validation after keys set, package account types for future syncing

* Add transient error checking for initial creddemtial validation

* fix command types

* Addressed nits from glorious person

* Amalgamate body within error when not between 2xx status, added btcmarket specific auth error check

* nit fix for glorious person

* Format fix

* removed unused code

* check transient first then validate if its an exchange specific authentication error, all others will be disregarded

* Addressed glorious nits

* Addressed glorious nits

* Moved account processing to updateaccountinfo func and added in fetch account info

* Add GRPC Account streaming (NOTE: could not complete until sync item added)

* RM exchange check

* Address xtda nits

* RM comment code

* Fix linter issues

* used most recent protoc version

* lbank linter issues fixed

* Addressed nits and changed len check to range in for loops

* Fixed timeout issue

* thrasher nits addressed

* add string holdings
This commit is contained in:
Ryan O'Hara-Reid
2020-01-31 12:09:24 +11:00
committed by GitHub
parent db23af2ed6
commit a32d16e1f5
75 changed files with 2010 additions and 857 deletions

View File

@@ -59,17 +59,6 @@ func dryrunParamInteraction(param string) {
}
}
// CheckExchangeExists returns true whether or not an exchange has already
// been loaded
func CheckExchangeExists(exchName string) bool {
for x := range Bot.Exchanges {
if strings.EqualFold(Bot.Exchanges[x].GetName(), exchName) {
return true
}
}
return false
}
// GetExchangeByName returns an exchange given an exchange name
func GetExchangeByName(exchName string) exchange.IBotExchange {
for x := range Bot.Exchanges {
@@ -86,7 +75,8 @@ func ReloadExchange(name string) error {
return ErrNoExchangesLoaded
}
if !CheckExchangeExists(name) {
e := GetExchangeByName(name)
if e == nil {
return ErrExchangeNotFound
}
@@ -95,7 +85,6 @@ func ReloadExchange(name string) error {
return err
}
e := GetExchangeByName(name)
e.Setup(exchCfg)
log.Debugf(log.ExchangeSys, "%s exchange reloaded successfully.\n", name)
return nil
@@ -107,7 +96,7 @@ func UnloadExchange(name string) error {
return ErrNoExchangesLoaded
}
if !CheckExchangeExists(name) {
if GetExchangeByName(name) == nil {
return ErrExchangeNotFound
}
@@ -139,7 +128,7 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error {
var exch exchange.IBotExchange
if len(Bot.Exchanges) > 0 {
if CheckExchangeExists(name) {
if GetExchangeByName(name) != nil {
return ErrExchangeAlreadyLoaded
}
}
@@ -288,55 +277,65 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error {
Bot.Exchanges = append(Bot.Exchanges, exch)
base := exch.GetBase()
if base.API.AuthenticatedSupport ||
base.API.AuthenticatedWebsocketSupport {
err = exch.ValidateCredentials()
if err != nil {
log.Warnf(log.ExchangeSys,
"%s: Cannot validate credentials, authenticated support has been disabled, Error: %s\n",
base.Name,
err)
base.API.AuthenticatedSupport = false
base.API.AuthenticatedWebsocketSupport = false
exchCfg.API.AuthenticatedSupport = false
exchCfg.API.AuthenticatedWebsocketSupport = false
}
}
if useWG {
exch.Start(wg)
} else {
wg := sync.WaitGroup{}
exch.Start(&wg)
wg.Wait()
tempWG := sync.WaitGroup{}
exch.Start(&tempWG)
tempWG.Wait()
}
return nil
}
// SetupExchanges sets up the exchanges used by the Bot
func SetupExchanges() {
var wg sync.WaitGroup
exchanges := Bot.Config.GetAllExchangeConfigs()
for x := range exchanges {
exch := exchanges[x]
if CheckExchangeExists(exch.Name) {
e := GetExchangeByName(exch.Name)
if e == nil {
log.Errorln(log.ExchangeSys, ErrExchangeNotFound)
continue
}
err := ReloadExchange(exch.Name)
configs := Bot.Config.GetAllExchangeConfigs()
for x := range configs {
if e := GetExchangeByName(configs[x].Name); e != nil {
err := ReloadExchange(configs[x].Name)
if err != nil {
log.Errorf(log.ExchangeSys, "ReloadExchange %s failed: %s\n", exch.Name, err)
log.Errorf(log.ExchangeSys, "ReloadExchange %s failed: %s\n", configs[x].Name, err)
continue
}
if !e.IsEnabled() {
UnloadExchange(exch.Name)
UnloadExchange(configs[x].Name)
continue
}
return
}
if !exch.Enabled && !Bot.Settings.EnableAllExchanges {
log.Debugf(log.ExchangeSys, "%s: Exchange support: Disabled\n", exch.Name)
if !configs[x].Enabled && !Bot.Settings.EnableAllExchanges {
log.Debugf(log.ExchangeSys, "%s: Exchange support: Disabled\n", configs[x].Name)
continue
}
err := LoadExchange(exch.Name, true, &wg)
err := LoadExchange(configs[x].Name, true, &wg)
if err != nil {
log.Errorf(log.ExchangeSys, "LoadExchange %s failed: %s\n", exch.Name, err)
log.Errorf(log.ExchangeSys, "LoadExchange %s failed: %s\n", configs[x].Name, err)
continue
}
log.Debugf(log.ExchangeSys,
"%s: Exchange support: Enabled (Authenticated API support: %s - Verbose mode: %s).\n",
exch.Name,
common.IsEnabled(exch.API.AuthenticatedSupport),
common.IsEnabled(exch.Verbose),
configs[x].Name,
common.IsEnabled(configs[x].API.AuthenticatedSupport),
common.IsEnabled(configs[x].Verbose),
)
}
wg.Wait()

View File

@@ -21,7 +21,7 @@ func SetupTest(t *testing.T) {
testSetup = true
}
if CheckExchangeExists(testExchange) {
if GetExchangeByName(testExchange) != nil {
return
}
err := LoadExchange(testExchange, false, nil)
@@ -31,7 +31,7 @@ func SetupTest(t *testing.T) {
}
func CleanupTest(t *testing.T) {
if !CheckExchangeExists(testExchange) {
if GetExchangeByName(testExchange) == nil {
return
}
@@ -45,11 +45,11 @@ func CleanupTest(t *testing.T) {
func TestCheckExchangeExists(t *testing.T) {
SetupTest(t)
if !CheckExchangeExists(testExchange) {
if GetExchangeByName(testExchange) == nil {
t.Errorf("TestGetExchangeExists: Unable to find exchange")
}
if CheckExchangeExists("Asdsad") {
if GetExchangeByName("Asdsad") != nil {
t.Errorf("TestGetExchangeExists: Non-existent exchange found")
}

View File

@@ -22,6 +22,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/dispatch"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
@@ -447,18 +448,21 @@ func GetSpecificTicker(p currency.Pair, exchangeName string, assetType asset.Ite
// GetCollatedExchangeAccountInfoByCoin collates individual exchange account
// information and turns into into a map string of
// exchange.AccountCurrencyInfo
func GetCollatedExchangeAccountInfoByCoin(exchAccounts []exchange.AccountInfo) map[currency.Code]exchange.AccountCurrencyInfo {
result := make(map[currency.Code]exchange.AccountCurrencyInfo)
for _, accounts := range exchAccounts {
for _, account := range accounts.Accounts {
for _, accountCurrencyInfo := range account.Currencies {
currencyName := accountCurrencyInfo.CurrencyName
avail := accountCurrencyInfo.TotalValue
onHold := accountCurrencyInfo.Hold
func GetCollatedExchangeAccountInfoByCoin(accounts []account.Holdings) map[currency.Code]account.Balance {
result := make(map[currency.Code]account.Balance)
for x := range accounts {
for y := range accounts[x].Accounts {
for z := range accounts[x].Accounts[y].Currencies {
currencyName := accounts[x].Accounts[y].Currencies[z].CurrencyName
avail := accounts[x].Accounts[y].Currencies[z].TotalValue
onHold := accounts[x].Accounts[y].Currencies[z].Hold
info, ok := result[currencyName]
if !ok {
accountInfo := exchange.AccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail}
accountInfo := account.Balance{
CurrencyName: currencyName,
Hold: onHold,
TotalValue: avail,
}
result[currencyName] = accountInfo
} else {
info.Hold += onHold
@@ -471,16 +475,6 @@ func GetCollatedExchangeAccountInfoByCoin(exchAccounts []exchange.AccountInfo) m
return result
}
// GetAccountCurrencyInfoByExchangeName returns info for an exchange
func GetAccountCurrencyInfoByExchangeName(accounts []exchange.AccountInfo, exchangeName string) (exchange.AccountInfo, error) {
for i := 0; i < len(accounts); i++ {
if accounts[i].Exchange == exchangeName {
return accounts[i], nil
}
}
return exchange.AccountInfo{}, ErrExchangeNotFound
}
// GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest
// price for a given currency pair and asset type
func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, assetType asset.Item) (string, error) {
@@ -504,23 +498,23 @@ func GetExchangeLowestPriceByCurrencyPair(p currency.Pair, assetType asset.Item)
}
// SeedExchangeAccountInfo seeds account info
func SeedExchangeAccountInfo(data []exchange.AccountInfo) {
if len(data) == 0 {
func SeedExchangeAccountInfo(accounts []account.Holdings) {
if len(accounts) == 0 {
return
}
port := portfolio.GetPortfolio()
for _, exchangeData := range data {
exchangeName := exchangeData.Exchange
var currencies []exchange.AccountCurrencyInfo
for _, account := range exchangeData.Accounts {
for _, info := range account.Currencies {
for x := range accounts {
exchangeName := accounts[x].Exchange
var currencies []account.Balance
for y := range accounts[x].Accounts {
for z := range accounts[x].Accounts[y].Currencies {
var update bool
for i := range currencies {
if info.CurrencyName == currencies[i].CurrencyName {
currencies[i].Hold += info.Hold
currencies[i].TotalValue += info.TotalValue
if accounts[x].Accounts[y].Currencies[z].CurrencyName == currencies[i].CurrencyName {
currencies[i].Hold += accounts[x].Accounts[y].Currencies[z].Hold
currencies[i].TotalValue += accounts[x].Accounts[y].Currencies[z].TotalValue
update = true
}
}
@@ -529,17 +523,17 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) {
continue
}
currencies = append(currencies, exchange.AccountCurrencyInfo{
CurrencyName: info.CurrencyName,
TotalValue: info.TotalValue,
Hold: info.Hold,
currencies = append(currencies, account.Balance{
CurrencyName: accounts[x].Accounts[y].Currencies[z].CurrencyName,
TotalValue: accounts[x].Accounts[y].Currencies[z].TotalValue,
Hold: accounts[x].Accounts[y].Currencies[z].Hold,
})
}
}
for _, total := range currencies {
currencyName := total.CurrencyName
total := total.TotalValue
for x := range currencies {
currencyName := currencies[x].CurrencyName
total := currencies[x].TotalValue
if !port.ExchangeAddressExists(exchangeName, currencyName) {
if total <= 0 {
@@ -768,7 +762,7 @@ func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts {
}
continue
}
individualExchange, err := individualBot.GetAccountInfo()
individualExchange, err := individualBot.FetchAccountInfo()
if err != nil {
log.Errorf(log.ExchangeSys, "Error encountered retrieving exchange account info for %s. Error %s\n",
individualBot.GetName(), err)

View File

@@ -7,7 +7,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
@@ -453,13 +453,13 @@ func TestGetSpecificTicker(t *testing.T) {
func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) {
SetupTestHelpers(t)
var exchangeInfo []exchange.AccountInfo
var info exchange.AccountInfo
var exchangeInfo []account.Holdings
info.Exchange = "Bitfinex"
info.Accounts = append(info.Accounts,
exchange.Account{
Currencies: []exchange.AccountCurrencyInfo{
var bitfinexHoldings account.Holdings
bitfinexHoldings.Exchange = "Bitfinex"
bitfinexHoldings.Accounts = append(bitfinexHoldings.Accounts,
account.SubAccount{
Currencies: []account.Balance{
{
CurrencyName: currency.BTC,
TotalValue: 100,
@@ -468,21 +468,27 @@ func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) {
},
})
exchangeInfo = append(exchangeInfo, info)
exchangeInfo = append(exchangeInfo, bitfinexHoldings)
info.Exchange = "Bitstamp"
info.Accounts = append(info.Accounts,
exchange.Account{
Currencies: []exchange.AccountCurrencyInfo{
var bitstampHoldings account.Holdings
bitstampHoldings.Exchange = "Bitstamp"
bitstampHoldings.Accounts = append(bitstampHoldings.Accounts,
account.SubAccount{
Currencies: []account.Balance{
{
CurrencyName: currency.LTC,
TotalValue: 100,
Hold: 0,
},
{
CurrencyName: currency.BTC,
TotalValue: 100,
Hold: 0,
},
},
})
exchangeInfo = append(exchangeInfo, info)
exchangeInfo = append(exchangeInfo, bitstampHoldings)
result := GetCollatedExchangeAccountInfoByCoin(exchangeInfo)
if len(result) == 0 {
@@ -504,40 +510,6 @@ func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) {
}
}
func TestGetAccountCurrencyInfoByExchangeName(t *testing.T) {
SetupTestHelpers(t)
var exchangeInfo []exchange.AccountInfo
var info exchange.AccountInfo
info.Exchange = "Bitfinex"
info.Accounts = append(info.Accounts,
exchange.Account{
Currencies: []exchange.AccountCurrencyInfo{
{
CurrencyName: currency.BTC,
TotalValue: 100,
Hold: 0,
},
},
})
exchangeInfo = append(exchangeInfo, info)
result, err := GetAccountCurrencyInfoByExchangeName(exchangeInfo, "Bitfinex")
if err != nil {
t.Fatal(err)
}
if result.Exchange != "Bitfinex" {
t.Fatal("Unexepcted result")
}
_, err = GetAccountCurrencyInfoByExchangeName(exchangeInfo, "ASDF")
if err != ErrExchangeNotFound {
t.Fatal("Unexepcted result")
}
}
func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) {
SetupTestHelpers(t)

View File

@@ -3,7 +3,7 @@ package engine
import (
"net/http"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
)
@@ -42,5 +42,5 @@ type EnabledExchangeCurrencies struct {
// AllEnabledExchangeAccounts holds all enabled accounts info
type AllEnabledExchangeAccounts struct {
Data []exchange.AccountInfo `json:"data"`
Data []account.Holdings `json:"data"`
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/database/models/postgres"
"github.com/thrasher-corp/gocryptotrader/database/models/sqlite3"
"github.com/thrasher-corp/gocryptotrader/database/repository/audit"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
@@ -448,7 +449,7 @@ func (s *RPCServer) GetAccountInfo(ctx context.Context, r *gctrpc.GetAccountInfo
return nil, errors.New("exchange is not loaded/doesn't exist")
}
resp, err := exch.GetAccountInfo()
resp, err := exch.FetchAccountInfo()
if err != nil {
return nil, err
}
@@ -470,6 +471,87 @@ func (s *RPCServer) GetAccountInfo(ctx context.Context, r *gctrpc.GetAccountInfo
return &gctrpc.GetAccountInfoResponse{Exchange: r.Exchange, Accounts: accounts}, nil
}
// GetAccountInfoStream streams an account balance for a specific exchange
func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream gctrpc.GoCryptoTrader_GetAccountInfoStreamServer) error {
if r.Exchange == "" {
return errors.New(errExchangeNameUnset)
}
exch := GetExchangeByName(r.Exchange)
if exch == nil {
return errors.New("exchange is not loaded/doesn't exist")
}
initAcc, err := exch.FetchAccountInfo()
if err != nil {
return err
}
var accounts []*gctrpc.Account
for x := range initAcc.Accounts {
var subAccounts []*gctrpc.AccountCurrencyInfo
for y := range initAcc.Accounts[x].Currencies {
subAccounts = append(subAccounts, &gctrpc.AccountCurrencyInfo{
Currency: initAcc.Accounts[x].Currencies[y].CurrencyName.String(),
TotalValue: initAcc.Accounts[x].Currencies[y].TotalValue,
Hold: initAcc.Accounts[x].Currencies[y].Hold,
})
}
accounts = append(accounts, &gctrpc.Account{
Id: initAcc.Accounts[x].ID,
Currencies: subAccounts,
})
}
err = stream.Send(&gctrpc.GetAccountInfoResponse{
Exchange: initAcc.Exchange,
Accounts: accounts,
})
if err != nil {
return err
}
pipe, err := account.SubscribeToExchangeAccount(r.Exchange)
if err != nil {
return err
}
defer pipe.Release()
for {
data, ok := <-pipe.C
if !ok {
return errors.New(errDispatchSystem)
}
acc := (*data.(*interface{})).(account.Holdings)
var accounts []*gctrpc.Account
for x := range acc.Accounts {
var subAccounts []*gctrpc.AccountCurrencyInfo
for y := range acc.Accounts[x].Currencies {
subAccounts = append(subAccounts, &gctrpc.AccountCurrencyInfo{
Currency: acc.Accounts[x].Currencies[y].CurrencyName.String(),
TotalValue: acc.Accounts[x].Currencies[y].TotalValue,
Hold: acc.Accounts[x].Currencies[y].Hold,
})
}
accounts = append(accounts, &gctrpc.Account{
Id: acc.Accounts[x].ID,
Currencies: subAccounts,
})
}
err := stream.Send(&gctrpc.GetAccountInfoResponse{
Exchange: acc.Exchange,
Accounts: accounts,
})
if err != nil {
return err
}
}
}
// GetConfig returns the bots config
func (s *RPCServer) GetConfig(ctx context.Context, r *gctrpc.GetConfigRequest) (*gctrpc.GetConfigResponse, error) {
return &gctrpc.GetConfigResponse{}, common.ErrNotYetImplemented