Skip to content

Commit

Permalink
Merge branch 'master' into coinbase_api_revamp
Browse files Browse the repository at this point in the history
  • Loading branch information
cranktakular committed Mar 6, 2025
2 parents 5de9d7f + 6ee26a7 commit d3bdad0
Show file tree
Hide file tree
Showing 46 changed files with 403 additions and 226 deletions.
13 changes: 7 additions & 6 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"flag"
"fmt"
"math"
"os"
"slices"
"strings"
Expand All @@ -23,15 +24,15 @@ func main() {

var in, out, keyStr string
var inplace bool
var version int
var version uint

fs := flag.NewFlagSet("config", flag.ExitOnError)
fs.Usage = func() { usage(fs) }
fs.StringVar(&in, "in", defaultCfgFile, "The config input file to process")
fs.StringVar(&out, "out", "[in].out", "The config output file")
fs.BoolVar(&inplace, "edit", false, "Edit; Save result to the original file")
fs.StringVar(&keyStr, "key", "", "The key to use for AES encryption")
fs.IntVar(&version, "version", 0, "The version to downgrade to")
fs.UintVar(&version, "version", 0, "The version to downgrade to")

cmd, args := parseCommand(os.Args[1:])
if cmd == "" {
Expand Down Expand Up @@ -85,13 +86,13 @@ func main() {
usage(fs)
os.Exit(3)
}
version = versions.LatestVersion
} else if version < 0 {
fmt.Fprintln(os.Stderr, "Error: version must be positive")
version = versions.UseLatestVersion
} else if version >= math.MaxUint16 {
fmt.Fprintln(os.Stderr, "Error: version must be less than 65535")
usage(fs)
os.Exit(3)
}
if data, err = versions.Manager.Deploy(context.Background(), data, version); err != nil {
if data, err = versions.Manager.Deploy(context.Background(), data, uint16(version)); err != nil {
fatal("Unable to " + cmd + " config; Error: " + err.Error())
}
if !isEncrypted {
Expand Down
1 change: 1 addition & 0 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ var (
ErrUnknownError = errors.New("unknown error")
ErrGettingField = errors.New("error getting field")
ErrSettingField = errors.New("error setting field")
ErrParsingWSField = errors.New("error parsing websocket field")
)

var (
Expand Down
2 changes: 1 addition & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1504,7 +1504,7 @@ func (c *Config) readConfig(d io.Reader) error {
}
}

if j, err = versions.Manager.Deploy(context.Background(), j, versions.LatestVersion); err != nil {
if j, err = versions.Manager.Deploy(context.Background(), j, versions.UseLatestVersion); err != nil {
return err
}

Expand Down
51 changes: 30 additions & 21 deletions config/versions/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"errors"
"fmt"
"log"
"math"
"os"
"slices"
"strconv"
Expand All @@ -25,17 +26,20 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
)

// LatestVersion used as version param to Deploy to automatically use the latest version
const LatestVersion = -1
// UseLatestVersion used as version param to Deploy to automatically use the latest version
const UseLatestVersion = math.MaxUint16

var (
errMissingVersion = errors.New("missing version")
errVersionIncompatible = errors.New("version does not implement ConfigVersion or ExchangeVersion")
errModifyingExchange = errors.New("error modifying exchange config")
errNoVersions = errors.New("error retrieving latest config version: No config versions are registered")
errApplyingVersion = errors.New("error applying version")
errConfigVersion = errors.New("version in config file is higher than the latest available version")
errTargetVersion = errors.New("target downgrade version is higher than the latest available version")
errMissingVersion = errors.New("missing version")
errVersionIncompatible = errors.New("version does not implement ConfigVersion or ExchangeVersion")
errModifyingExchange = errors.New("error modifying exchange config")
errNoVersions = errors.New("error retrieving latest config version: No config versions are registered")
errApplyingVersion = errors.New("error applying version")
errTargetVersion = errors.New("target downgrade version is higher than the latest available version")
errConfigVersion = errors.New("invalid version in config")
errConfigVersionUnavail = errors.New("version is higher than the latest available version")
errConfigVersionNegative = errors.New("version is negative")
errConfigVersionMax = errors.New("version is above max versions")
)

// ConfigVersion is a version that affects the general configuration
Expand All @@ -61,9 +65,9 @@ type manager struct {
var Manager = &manager{}

// Deploy upgrades or downgrades the config between versions
// Pass LatestVersion for version to use the latest version automatically
// Pass UseLatestVersion for version to use the latest version automatically
// Prints an error an exits if the config file version or version param is not registered
func (m *manager) Deploy(ctx context.Context, j []byte, version int) ([]byte, error) {
func (m *manager) Deploy(ctx context.Context, j []byte, version uint16) ([]byte, error) {
if err := m.checkVersions(); err != nil {
return j, err
}
Expand All @@ -74,28 +78,33 @@ func (m *manager) Deploy(ctx context.Context, j []byte, version int) ([]byte, er
}

target := latest
if version != LatestVersion {
if version != UseLatestVersion {
target = version
}

m.m.RLock()
defer m.m.RUnlock()

current64, err := jsonparser.GetInt(j, "version")
current := int(current64)
switch {
case errors.Is(err, jsonparser.KeyPathNotFoundError):
current = -1
// With no version first upgrade is to Version1; current64 is already 0
case err != nil:
return j, fmt.Errorf("%w `version`: %w", common.ErrGettingField, err)
return j, fmt.Errorf("%w: %w `version`: %w", errConfigVersion, common.ErrGettingField, err)
case current64 < 0:
return j, fmt.Errorf("%w: %w `version`: `%d`", errConfigVersion, errConfigVersionNegative, current64)
case current64 >= UseLatestVersion:
return j, fmt.Errorf("%w: %w `version`: `%d`", errConfigVersion, errConfigVersionMax, current64)
}
current := uint16(current64)

switch {
case target == current:
return j, nil
case latest < current:
warnVersionNotRegistered(current, latest, errConfigVersion)
return j, errConfigVersion
err := fmt.Errorf("%w: %w", errConfigVersion, errConfigVersionUnavail)
warnVersionNotRegistered(current, latest, err)
return j, err
case target > latest:
warnVersionNotRegistered(target, latest, errTargetVersion)
return j, errTargetVersion
Expand Down Expand Up @@ -135,7 +144,7 @@ func (m *manager) Deploy(ctx context.Context, j []byte, version int) ([]byte, er
current = patchVersion - 1
}

if j, err = jsonparser.Set(j, []byte(strconv.Itoa(current)), "version"); err != nil {
if j, err = jsonparser.Set(j, []byte(strconv.FormatUint(uint64(current), 10)), "version"); err != nil {
return j, fmt.Errorf("%w `version` during %s %v: %w", common.ErrSettingField, action, patchVersion, err)
}
}
Expand Down Expand Up @@ -201,13 +210,13 @@ func (m *manager) registerVersion(ver int, v any) {
}

// latest returns the highest version number
func (m *manager) latest() (int, error) {
func (m *manager) latest() (uint16, error) {
m.m.RLock()
defer m.m.RUnlock()
if len(m.versions) == 0 {
return 0, errNoVersions
}
return len(m.versions) - 1, nil
return uint16(len(m.versions)) - 1, nil
}

// checkVersions ensures that registered versions are consistent
Expand All @@ -227,7 +236,7 @@ func (m *manager) checkVersions() error {
return nil
}

func warnVersionNotRegistered(current, latest int, msg error) {
func warnVersionNotRegistered(current, latest uint16, msg error) {
fmt.Fprintf(os.Stderr, `
%s ('%d' > '%d')
Switch back to the version of GoCryptoTrader containing config version '%d' and run:
Expand Down
45 changes: 24 additions & 21 deletions config/versions/versions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,64 +13,67 @@ import (
func TestDeploy(t *testing.T) {
t.Parallel()
m := manager{}
_, err := m.Deploy(context.Background(), []byte(``), LatestVersion)
_, err := m.Deploy(context.Background(), []byte(``), UseLatestVersion)
assert.ErrorIs(t, err, errNoVersions)

m.registerVersion(1, &TestVersion1{})
_, err = m.Deploy(context.Background(), []byte(``), LatestVersion)
_, err = m.Deploy(context.Background(), []byte(``), UseLatestVersion)
require.ErrorIs(t, err, errVersionIncompatible)

m = manager{}

m.registerVersion(0, &Version0{})
_, err = m.Deploy(context.Background(), []byte(`not an object`), LatestVersion)
m.registerVersion(1, &Version1{})
_, err = m.Deploy(context.Background(), []byte(`not an object`), UseLatestVersion)
require.ErrorIs(t, err, jsonparser.KeyPathNotFoundError, "Must throw the correct error trying to add version to bad json")
require.ErrorIs(t, err, common.ErrSettingField, "Must throw the correct error trying to add version to bad json")
require.ErrorContains(t, err, "version", "Must throw the correct error trying to add version to bad json")

_, err = m.Deploy(context.Background(), []byte(`{"version":"not an int"}`), LatestVersion)
_, err = m.Deploy(context.Background(), []byte(`{"version":"not an int"}`), UseLatestVersion)
require.ErrorIs(t, err, common.ErrGettingField, "Must throw the correct error trying to get version from bad json")

in := []byte(`{"version":0,"exchanges":[{"name":"Juan"}]}`)
j, err := m.Deploy(context.Background(), in, LatestVersion)
require.NoError(t, err)
assert.Equal(t, string(in), string(j))
_, err = m.Deploy(context.Background(), []byte(`{"version":65535}`), UseLatestVersion)
require.ErrorIs(t, err, errConfigVersionMax, "Must throw the correct error when version is too high")

m.registerVersion(1, &Version1{})
j, err = m.Deploy(context.Background(), in, LatestVersion)
_, err = m.Deploy(context.Background(), []byte(`{"version":-1}`), UseLatestVersion)
require.ErrorIs(t, err, errConfigVersionNegative, "Must throw the correct error when version is negative")

in := []byte(`{"version":0,"exchanges":[{"name":"Juan"}]}`)
j, err := m.Deploy(context.Background(), in, UseLatestVersion)
require.NoError(t, err)
assert.Contains(t, string(j), `"version": 1`)

j2, err := m.Deploy(context.Background(), j, UseLatestVersion)
require.NoError(t, err, "Deploy the same version again must not error")
require.Equal(t, string(j2), string(j), "Deploy the same version again must not change config")

_, err = m.Deploy(context.Background(), j, 2)
assert.ErrorIs(t, err, errTargetVersion, "Downgrade to a unregistered version should not be allowed")

m.versions = append(m.versions, &TestVersion2{ConfigErr: true, ExchErr: false})
_, err = m.Deploy(context.Background(), j, LatestVersion)
_, err = m.Deploy(context.Background(), j, UseLatestVersion)
require.ErrorIs(t, err, errUpgrade)

m.versions[len(m.versions)-1] = &TestVersion2{ConfigErr: false, ExchErr: true}
_, err = m.Deploy(context.Background(), in, LatestVersion)
_, err = m.Deploy(context.Background(), in, UseLatestVersion)
require.Implements(t, (*ExchangeVersion)(nil), m.versions[1])
require.ErrorIs(t, err, errUpgrade)

j2, err := m.Deploy(context.Background(), j, 0)
j2, err = m.Deploy(context.Background(), j, 0)
require.NoError(t, err)
assert.Contains(t, string(j2), `"version": 0`, "Explicit downgrade should work correctly")

m.versions = m.versions[:1]
_, err = m.Deploy(context.Background(), j, LatestVersion)
assert.ErrorIs(t, err, errConfigVersion, "Config version ahead of latest version should error")

_, err = m.Deploy(context.Background(), j, 0)
assert.ErrorIs(t, err, errConfigVersion, "Config version ahead of latest version should error")
_, err = m.Deploy(context.Background(), j, UseLatestVersion)
assert.ErrorIs(t, err, errConfigVersionUnavail, "Config version ahead of latest version should error")
}

// TestExchangeDeploy exercises exchangeDeploy
// There are a number of error paths we can't currently cover without exposing unacceptable risks to the hot-paths as well
func TestExchangeDeploy(t *testing.T) {
t.Parallel()
m := manager{}
_, err := m.Deploy(context.Background(), []byte(``), LatestVersion)
_, err := m.Deploy(context.Background(), []byte(``), UseLatestVersion)
assert.ErrorIs(t, err, errNoVersions)

v := &TestVersion2{}
Expand Down Expand Up @@ -113,10 +116,10 @@ func TestLatest(t *testing.T) {
m.registerVersion(1, &Version1{})
v, err := m.latest()
require.NoError(t, err)
assert.Equal(t, 1, v)
assert.Equal(t, uint16(1), v)

m.registerVersion(2, &Version2{})
v, err = m.latest()
require.NoError(t, err)
assert.Equal(t, 2, v)
assert.Equal(t, uint16(2), v)
}
2 changes: 1 addition & 1 deletion exchanges/binance/binance_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ func (b *Binance) GetRecentTrades(ctx context.Context, p currency.Pair, a asset.
}

if b.IsSaveTradeDataEnabled() {
err := trade.AddTradesToBuffer(b.Name, resp...)
err := trade.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
Expand Down
5 changes: 3 additions & 2 deletions exchanges/binanceus/binanceus_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,8 @@ func (bi *Binanceus) UpdateOrderbook(ctx context.Context, pair currency.Pair, as

orderbookNew, err := bi.GetOrderBookDepth(ctx, &OrderBookDataRequestParams{
Symbol: pair,
Limit: 1000})
Limit: 1000,
})
if err != nil {
return book, err
}
Expand Down Expand Up @@ -450,7 +451,7 @@ func (bi *Binanceus) GetRecentTrades(ctx context.Context, p currency.Pair, asset
}

if bi.IsSaveTradeDataEnabled() {
err := trade.AddTradesToBuffer(bi.Name, resp...)
err := trade.AddTradesToBuffer(resp...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion exchanges/bitfinex/bitfinex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1998,7 +1998,7 @@ func TestGetErrResp(t *testing.T) {
seen++
switch seen {
case 1: // no event
assert.ErrorIs(t, testErr, errParsingWSField, "Message with no event Should get correct error type")
assert.ErrorIs(t, testErr, common.ErrParsingWSField, "Message with no event should get correct error type")
assert.ErrorContains(t, testErr, "'event'", "Message with no event error should contain missing field name")
assert.ErrorContains(t, testErr, "nightjar", "Message with no event error should contain the message")
case 2: // with {} for event
Expand Down
Loading

0 comments on commit d3bdad0

Please sign in to comment.