Skip to content

Commit

Permalink
Merge pull request #392 from BuxOrg/chore-431-miners-apis-config
Browse files Browse the repository at this point in the history
chore(BUX-431) Configure & select `mapi` or `arc` with arc-gorillapool as default
  • Loading branch information
chris-4chain authored Jan 5, 2024
2 parents 20cc392 + db928ab commit c857560
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 65 deletions.
16 changes: 6 additions & 10 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,12 @@ _new_relic:
domain_name: domain.com
enabled: false
license_key: BOGUS-LICENSE-KEY-1234567890987654321234
nodes:
broadcast_client_apis: []
minercraft_api: mAPI
minercraft_custom_apis:
- minerid: 03e92d3e5c3f7bd945dfbf48e7a99393b1bfb3f11f380ae30d286e7ff2aec5a270
apis:
- token: mainnet_3af382fadbc448b15cc4133242ac2621
url: https://merchantapi.taal.com
type: mAPI
use_mapi_fee_quotes: true
# Prefixed with "_", because it will be configured by default
_nodes:
protocol: arc
apis:
- arc_url: https://arc.gorillapool.io
token:
# Prefixed with "_", because it's unused by default
_notifications:
enabled: false
Expand Down
33 changes: 18 additions & 15 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/BuxOrg/bux/taskmanager"
"github.com/mrz1836/go-cachestore"
"github.com/mrz1836/go-datastore"
"github.com/tonicpow/go-minercraft/v2"
)

// Config constants used for bux-server
Expand Down Expand Up @@ -149,20 +148,24 @@ type NewRelicConfig struct {

// NodesConfig consists of blockchain nodes (such as Minercraft and Arc) configuration
type NodesConfig struct {
// UseMapiFeeQuotes is a flag that says whether bux should use fee quotes from mAPI.
UseMapiFeeQuotes bool `json:"use_mapi_fee_quotes" mapstructure:"use_mapi_fee_quotes"`
// MinercraftAPI is a string that holds the url of mAPI.
MinercraftAPI string `json:"minercraft_api" mapstructure:"minercraft_api"`
// MinercraftCustomAPIs is a slice of Minercraft custom miners APIs.
MinercraftCustomAPIs []*minercraft.MinerAPIs `json:"minercraft_custom_apis" mapstructure:"minercraft_custom_apis"`
// BroadcastClientAPIs is a slice of Broadcast Client custom miners APIs.
BroadcastClientAPIs []*BroadcastClientAPI `json:"broadcast_client_apis" mapstructure:"broadcast_client_apis"`
}

// BroadcastClientAPI is a URL-Token pair for Broadcast Client; Token is optional
type BroadcastClientAPI struct {
Token string `json:"token,omitempty"`
URL string `json:"url,omitempty"`
Protocol NodesProtocol `json:"protocol" mapstructure:"protocol"`
Apis []*MinerAPI `json:"apis" mapstructure:"apis"`
Mapi *MapiConfig `json:"mapi" mapstructure:"mapi"`
}

// MinerAPI holds connection info for a single miner endpoint
type MinerAPI struct {
Token string `json:"token" mapstructure:"token"`
ArcURL string `json:"arc_url" mapstructure:"arc_url"`
MapiURL string `json:"mapi_url" mapstructure:"mapi_url"`

// MinerID is not used with ARC potocol
MinerID string `json:"minerid" mapstructure:"minerid"`
}

// MapiConfig holds mApi-specific configuration
type MapiConfig struct {
UseFeeQuotes bool `json:"use_fee_quotes" mapstructure:"use_fee_quotes"`
}

// NotificationsConfig is the configuration for notifications
Expand Down
17 changes: 12 additions & 5 deletions config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"time"

"github.com/mrz1836/go-datastore"
"github.com/tonicpow/go-minercraft/v2"
)

func getDefaultAppConfig() *AppConfig {
Expand Down Expand Up @@ -116,10 +115,18 @@ func getNewRelicDefaults() *NewRelicConfig {

func getNodesDefaults() *NodesConfig {
return &NodesConfig{
UseMapiFeeQuotes: true,
MinercraftAPI: "mAPI",
MinercraftCustomAPIs: []*minercraft.MinerAPIs{},
BroadcastClientAPIs: []*BroadcastClientAPI{},
Protocol: NodesProtocolArc,
Apis: []*MinerAPI{
{
ArcURL: "https://arc.gorillapool.io",
// GorillaPool does not support querying (Merkle proofs)
Token: "",
MinerID: "03ad780153c47df915b3d2e23af727c68facaca4facd5f155bf5018b979b9aeb83",
},
},
Mapi: &MapiConfig{
UseFeeQuotes: true,
},
}
}

Expand Down
67 changes: 67 additions & 0 deletions config/nodes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package config

import (
"errors"

broadcastclient "github.com/bitcoin-sv/go-broadcast-client/broadcast/broadcast-client"
"github.com/tonicpow/go-minercraft/v2"
)

// NodesProtocol is the protocol/api_type used to communicate with the miners
type NodesProtocol string

const (
// NodesProtocolMapi represents the mapi protocol provided by minercraft
NodesProtocolMapi NodesProtocol = "mapi"

// NodesProtocolArc represents the arc protocol provided by go-broadcast-client
NodesProtocolArc NodesProtocol = "arc"
)

// Validate whether the protocol is known
func (n NodesProtocol) Validate() error {
switch n {
case NodesProtocolMapi, NodesProtocolArc:
return nil
default:
return errors.New("invalid nodes protocol")
}
}

func (nodes *NodesConfig) toMinercraftMapi() []*minercraft.MinerAPIs {
minerApis := []*minercraft.MinerAPIs{}
if nodes.Apis != nil {
for _, api := range nodes.Apis {
if api.MapiURL == "" {
continue
}
minerApis = append(minerApis, &minercraft.MinerAPIs{
MinerID: api.MinerID,
APIs: []minercraft.API{
{
Token: api.Token,
URL: api.MapiURL,
Type: minercraft.MAPI,
},
},
})
}
}
return minerApis
}

func (nodes *NodesConfig) toBroadcastClientArc() []*broadcastclient.ArcClientConfig {
minerApis := []*broadcastclient.ArcClientConfig{}
if nodes.Apis != nil {
for _, cfg := range nodes.Apis {
if cfg.ArcURL == "" {
continue
}
minerApis = append(minerApis, &broadcastclient.ArcClientConfig{
Token: cfg.Token,
APIUrl: cfg.ArcURL,
})
}
}
return minerApis
}
59 changes: 30 additions & 29 deletions config/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"net/url"
"strings"
"time"

"github.com/BuxOrg/bux"
Expand All @@ -20,7 +19,6 @@ import (
"github.com/mrz1836/go-datastore"
"github.com/newrelic/go-agent/v3/newrelic"
"github.com/rs/zerolog"
"github.com/tonicpow/go-minercraft/v2"
)

// AppServices is the loaded services via config
Expand Down Expand Up @@ -187,24 +185,12 @@ func (s *AppServices) loadBux(ctx context.Context, appConfig *AppConfig, testMod
options = append(options, bux.WithNotifications(appConfig.Notifications.WebhookEndpoint))
}

if appConfig.Nodes.UseMapiFeeQuotes {
options = append(options, bux.WithMinercraftFeeQuotes())
}

if strings.EqualFold(appConfig.Nodes.MinercraftAPI, string(minercraft.MAPI)) {
options = append(options, bux.WithMAPI())
}

if strings.EqualFold(appConfig.Nodes.MinercraftAPI, string(minercraft.Arc)) {
options = append(options, bux.WithArc())
if appConfig.Nodes.Protocol == NodesProtocolMapi {
options = loadMinercraftMapi(appConfig, options)
} else if appConfig.Nodes.Protocol == NodesProtocolArc {
options = loadBroadcastClientArc(appConfig, options, logger)
}

if appConfig.Nodes.MinercraftCustomAPIs != nil {
options = append(options, bux.WithMinercraftAPIs(appConfig.Nodes.MinercraftCustomAPIs))
}

options = loadBroadcastClientAPI(appConfig, options)

// Create the new client
s.Bux, err = bux.NewClient(ctx, options...)

Expand Down Expand Up @@ -377,18 +363,33 @@ func loadTaskManager(appConfig *AppConfig, options []bux.ClientOps) []bux.Client
return options
}

func loadBroadcastClientAPI(appConfig *AppConfig, options []bux.ClientOps) []bux.ClientOps {
if appConfig.Nodes.BroadcastClientAPIs != nil {
func loadBroadcastClientArc(appConfig *AppConfig, options []bux.ClientOps, logger *zerolog.Logger) []bux.ClientOps {
builder := broadcastclient.Builder()
var bcLogger zerolog.Logger
if logger == nil {
bcLogger = zerolog.Nop()
} else {
bcLogger = logger.With().Str("service", "broadcast-client").Logger()
}
for _, arcClient := range appConfig.Nodes.toBroadcastClientArc() {
builder.WithArc(*arcClient, &bcLogger)
}
broadcastClient := builder.Build()
options = append(
options,
bux.WithBroadcastClient(broadcastClient),
)
return options
}

builder := broadcastclient.Builder()
for _, cfg := range appConfig.Nodes.BroadcastClientAPIs {
builder.WithArc(broadcastclient.ArcClientConfig{
Token: cfg.Token,
APIUrl: cfg.URL,
})
}
broadcastClient := builder.Build()
options = append(options, bux.WithBroadcastClient(broadcastClient))
func loadMinercraftMapi(appConfig *AppConfig, options []bux.ClientOps) []bux.ClientOps {
options = append(
options,
bux.WithMAPI(),
bux.WithMinercraftAPIs(appConfig.Nodes.toMinercraftMapi()),
)
if appConfig.Nodes.Mapi != nil && appConfig.Nodes.Mapi.UseFeeQuotes {
options = append(options, bux.WithMinercraftFeeQuotes())
}
return options
}
44 changes: 44 additions & 0 deletions config/validate_nodes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package config

import (
"errors"
"slices"
)

// Validate checks the configuration for specific rules
func (n *NodesConfig) Validate() error {
if n == nil {
return errors.New("nodes are not configured")
}

err := n.Protocol.Validate()
if err != nil {
return err
}

if n.Apis == nil || len(n.Apis) == 0 {
return errors.New("no miner apis configured")
}

// check if at least one mapi url is configured
if n.Protocol == NodesProtocolMapi {
found := slices.IndexFunc(n.Apis, func(el *MinerAPI) bool {
return el.MapiURL != ""
})
if found == -1 {
return errors.New("no mapi urls configured")
}
}

// check if at least one arc url is configured
if n.Protocol == NodesProtocolArc {
found := slices.IndexFunc(n.Apis, func(el *MinerAPI) bool {
return el.ArcURL != ""
})
if found == -1 {
return errors.New("no arc urls configured")
}
}

return nil
}
60 changes: 60 additions & 0 deletions config/validate_nodes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package config

import (
"testing"

"github.com/stretchr/testify/assert"
)

// TestNewRelicConfig_Validate will test the method Validate()
func TestNodesConfig_Validate(t *testing.T) {
t.Parallel()

t.Run("valid default nodes config", func(t *testing.T) {
n := getNodesDefaults()
assert.NoError(t, n.Validate())
})

t.Run("wrong protocol", func(t *testing.T) {
n := getNodesDefaults()
n.Protocol = "wrong"
assert.Error(t, n.Validate())
})

t.Run("empty list of apis", func(t *testing.T) {
n := getNodesDefaults()

n.Apis = nil
assert.Error(t, n.Validate())

n.Apis = []*MinerAPI{}
assert.Error(t, n.Validate())
})

t.Run("no mapi url", func(t *testing.T) {
n := getNodesDefaults()

n.Apis = []*MinerAPI{
{
MapiURL: "",
},
}
assert.Error(t, n.Validate())
})

t.Run("no mapi url", func(t *testing.T) {
n := getNodesDefaults()

n.Protocol = NodesProtocolMapi
n.Apis[0].MapiURL = ""
assert.Error(t, n.Validate())
})

t.Run("no arc url", func(t *testing.T) {
n := getNodesDefaults()

n.Protocol = NodesProtocolArc
n.Apis[0].ArcURL = ""
assert.Error(t, n.Validate())
})
}
4 changes: 2 additions & 2 deletions go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c857560

Please sign in to comment.