Skip to content
This repository has been archived by the owner on Apr 2, 2024. It is now read-only.

feat(BUX-51): go-minercraft v2 #339

Merged
merged 1 commit into from
Jul 24, 2023
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
2 changes: 1 addition & 1 deletion chainstate/broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func createActiveProviders(c *Client, txID, txHex string) []txBroadcastProvider
providers := make([]txBroadcastProvider, 0, 10)

if shouldBroadcastWithMAPI(c) {
for _, miner := range c.options.config.mAPI.broadcastMiners {
for _, miner := range c.options.config.minercraftConfig.broadcastMiners {
if miner == nil {
continue
}
Expand Down
2 changes: 1 addition & 1 deletion chainstate/broadcast_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"

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

// generic broadcast provider
Expand Down
2 changes: 1 addition & 1 deletion chainstate/broadcast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tonicpow/go-minercraft"
"github.com/tonicpow/go-minercraft/v2"
)

func Test_doesErrorContain(t *testing.T) {
Expand Down
86 changes: 51 additions & 35 deletions chainstate/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"time"

"github.com/BuxOrg/bux/utils"
"github.com/libsv/go-bt/v2"
zLogger "github.com/mrz1836/go-logger"
"github.com/mrz1836/go-nownodes"
"github.com/mrz1836/go-whatsonchain"
"github.com/newrelic/go-agent/v3/newrelic"
"github.com/tonicpow/go-minercraft"
"github.com/tonicpow/go-minercraft/v2"
"github.com/tonicpow/go-minercraft/v2/apis/mapi"
)

type (
Expand All @@ -35,7 +37,7 @@ type (
syncConfig struct {
excludedProviders []string // List of provider names
httpClient HTTPInterface // Custom HTTP client (Minercraft, WOC)
mAPI *mAPIConfig // mAPI configuration
minercraftConfig *minercraftConfig // minercraftConfig configuration
minercraft minercraft.ClientInterface // Minercraft client
network Network // Current network (mainnet, testnet, stn)
nowNodes nownodes.ClientInterface // NOWNodes client
Expand All @@ -45,12 +47,14 @@ type (
whatsOnChainAPIKey string // If set, use this key
}

// mAPIConfig is specific for mAPI configuration
mAPIConfig struct {
broadcastMiners []*Miner // List of loaded miners for broadcasting
queryMiners []*Miner // List of loaded miners for querying transactions
feeUnit *utils.FeeUnit // The lowest fees among all miners
mapiFeeQuotesEnabled bool // If set, feeUnit will be updated with fee quotes from miner's mAPI
// minercraftConfig is specific for minercraft configuration
minercraftConfig struct {
broadcastMiners []*Miner // List of loaded miners for broadcasting
queryMiners []*Miner // List of loaded miners for querying transactions
feeUnit *utils.FeeUnit // The lowest fees among all miners
minercraftFeeQuotes bool // If set, feeUnit will be updated with fee quotes from miner's mAPI
apiType minercraft.APIType // MinerCraft APIType(ARC/mAPI)
minerAPIs []*minercraft.MinerAPIs // List of miners APIs
}

// Miner is the internal chainstate miner (wraps Minercraft miner with more information)
Expand Down Expand Up @@ -187,21 +191,21 @@ func (c *Client) QueryTimeout() time.Duration {

// BroadcastMiners will return the broadcast miners
func (c *Client) BroadcastMiners() []*Miner {
return c.options.config.mAPI.broadcastMiners
return c.options.config.minercraftConfig.broadcastMiners
}

// QueryMiners will return the query miners
func (c *Client) QueryMiners() []*Miner {
return c.options.config.mAPI.queryMiners
return c.options.config.minercraftConfig.queryMiners
}

// FeeUnit will return feeUnit
func (c *Client) FeeUnit() *utils.FeeUnit {
return c.options.config.mAPI.feeUnit
return c.options.config.minercraftConfig.feeUnit
}

func (c *Client) isMapiFeeQuotesEnabled() bool {
return c.options.config.mAPI.mapiFeeQuotesEnabled
func (c *Client) isMinercraftFeeQuotesEnabled() bool {
return c.options.config.minercraftConfig.minercraftFeeQuotes
}

// ValidateMiners will check if miner is reacheble by requesting its FeeQuote
Expand All @@ -214,7 +218,7 @@ func (c *Client) ValidateMiners(ctx context.Context) {

var wg sync.WaitGroup
// Loop all broadcast miners
for index := range c.options.config.mAPI.broadcastMiners {
for index := range c.options.config.minercraftConfig.broadcastMiners {
wg.Add(1)
go func(
ctx context.Context, client *Client,
Expand All @@ -223,60 +227,72 @@ func (c *Client) ValidateMiners(ctx context.Context) {
defer wg.Done()
// Get the fee quote using the miner
// Switched from policyQuote to feeQuote as gorillapool doesn't have such endpoint
quote, err := c.Minercraft().FeeQuote(ctx, miner.Miner)
if err != nil {
client.options.logger.Error(ctx, fmt.Sprintf("No FeeQuote response from miner %s", miner.Miner.Name))
miner.FeeUnit = nil
return
}
var fee *bt.Fee
if c.Minercraft().APIType() == minercraft.MAPI {
quote, err := c.Minercraft().FeeQuote(ctx, miner.Miner)
if err != nil {
client.options.logger.Error(ctx, fmt.Sprintf("No FeeQuote response from miner %s", miner.Miner.Name))
miner.FeeUnit = nil
return
}

fee = quote.Quote.GetFee(mapi.FeeTypeData)
if fee == nil {
client.options.logger.Error(ctx, fmt.Sprintf("Fee is missing in %s's FeeQuote response", miner.Miner.Name))
return
}
// Arc doesn't support FeeQuote right now(2023.07.21), that's why PolicyQuote is used
} else if c.Minercraft().APIType() == minercraft.Arc {
quote, err := c.Minercraft().PolicyQuote(ctx, miner.Miner)
if err != nil {
client.options.logger.Error(ctx, fmt.Sprintf("No FeeQuote response from miner %s", miner.Miner.Name))
miner.FeeUnit = nil
return
}

// Get the fee and set the fee
fee := quote.Quote.GetFee(minercraft.FeeTypeData)
if fee == nil {
client.options.logger.Error(ctx, fmt.Sprintf("Fee is missing in %s's FeeQuote response", miner.Miner.Name))
return
fee = quote.Quote.Fees[0]
}
if c.isMapiFeeQuotesEnabled() {
if c.isMinercraftFeeQuotesEnabled() {
miner.FeeUnit = &utils.FeeUnit{
Satoshis: fee.MiningFee.Satoshis,
Bytes: fee.MiningFee.Bytes,
}
miner.FeeLastChecked = time.Now().UTC()
}
}(ctxWithCancel, c, &wg, c.options.config.mAPI.broadcastMiners[index])
}(ctxWithCancel, c, &wg, c.options.config.minercraftConfig.broadcastMiners[index])
}
wg.Wait()

c.DeleteUnreacheableMiners()

if c.isMapiFeeQuotesEnabled() {
if c.isMinercraftFeeQuotesEnabled() {
c.SetLowestFees()
}
}

// SetLowestFees takes the lowest fees among all miners and sets them as the feeUnit for future transactions
func (c *Client) SetLowestFees() {
minFees := DefaultFee
for _, m := range c.options.config.mAPI.broadcastMiners {
for _, m := range c.options.config.minercraftConfig.broadcastMiners {
if float64(minFees.Satoshis)/float64(minFees.Bytes) > float64(m.FeeUnit.Satoshis)/float64(m.FeeUnit.Bytes) {
minFees = m.FeeUnit
}
}
c.options.config.mAPI.feeUnit = minFees
c.options.config.minercraftConfig.feeUnit = minFees
}

// DeleteUnreacheableMiners deletes miners which can't be reacheable from config
func (c *Client) DeleteUnreacheableMiners() {
validMinerIndex := 0
for _, miner := range c.options.config.mAPI.broadcastMiners {
for _, miner := range c.options.config.minercraftConfig.broadcastMiners {
if miner.FeeUnit != nil {
c.options.config.mAPI.broadcastMiners[validMinerIndex] = miner
c.options.config.minercraftConfig.broadcastMiners[validMinerIndex] = miner
validMinerIndex++
}
}
// Prevent memory leak by erasing truncated miners
for i := validMinerIndex; i < len(c.options.config.mAPI.broadcastMiners); i++ {
c.options.config.mAPI.broadcastMiners[i] = nil
for i := validMinerIndex; i < len(c.options.config.minercraftConfig.broadcastMiners); i++ {
c.options.config.minercraftConfig.broadcastMiners[i] = nil
}
c.options.config.mAPI.broadcastMiners = c.options.config.mAPI.broadcastMiners[:validMinerIndex]
c.options.config.minercraftConfig.broadcastMiners = c.options.config.minercraftConfig.broadcastMiners[:validMinerIndex]
}
22 changes: 12 additions & 10 deletions chainstate/client_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/mrz1836/go-nownodes"
"github.com/mrz1836/go-whatsonchain"
"github.com/newrelic/go-agent/v3/newrelic"
"github.com/tonicpow/go-minercraft"
"github.com/tonicpow/go-minercraft/v2"
)

// defaultMinercraftOptions will create the defaults
Expand Down Expand Up @@ -47,24 +47,26 @@ func (c *Client) startMinerCraft(ctx context.Context) (err error) {
var loadedMiners []string

// Loop all broadcast miners and append to the list of miners
for i := range c.options.config.mAPI.broadcastMiners {
if !utils.StringInSlice(c.options.config.mAPI.broadcastMiners[i].Miner.MinerID, loadedMiners) {
optionalMiners = append(optionalMiners, c.options.config.mAPI.broadcastMiners[i].Miner)
loadedMiners = append(loadedMiners, c.options.config.mAPI.broadcastMiners[i].Miner.MinerID)
for i := range c.options.config.minercraftConfig.broadcastMiners {
if !utils.StringInSlice(c.options.config.minercraftConfig.broadcastMiners[i].Miner.MinerID, loadedMiners) {
optionalMiners = append(optionalMiners, c.options.config.minercraftConfig.broadcastMiners[i].Miner)
loadedMiners = append(loadedMiners, c.options.config.minercraftConfig.broadcastMiners[i].Miner.MinerID)
}
}

// Loop all query miners and append to the list of miners
for i := range c.options.config.mAPI.queryMiners {
if !utils.StringInSlice(c.options.config.mAPI.queryMiners[i].Miner.MinerID, loadedMiners) {
optionalMiners = append(optionalMiners, c.options.config.mAPI.queryMiners[i].Miner)
loadedMiners = append(loadedMiners, c.options.config.mAPI.queryMiners[i].Miner.MinerID)
for i := range c.options.config.minercraftConfig.queryMiners {
if !utils.StringInSlice(c.options.config.minercraftConfig.queryMiners[i].Miner.MinerID, loadedMiners) {
optionalMiners = append(optionalMiners, c.options.config.minercraftConfig.queryMiners[i].Miner)
loadedMiners = append(loadedMiners, c.options.config.minercraftConfig.queryMiners[i].Miner.MinerID)
}
}
c.options.config.minercraft, err = minercraft.NewClient(
c.defaultMinercraftOptions(),
c.HTTPClient(),
optionalMiners, // If empty, it will use the default miners from Minercraft
c.options.config.minercraftConfig.apiType,
optionalMiners,
c.options.config.minercraftConfig.minerAPIs,
)
}

Expand Down
85 changes: 30 additions & 55 deletions chainstate/client_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ package chainstate

import (
"context"
"reflect"
"strings"
"time"

zLogger "github.com/mrz1836/go-logger"
"github.com/mrz1836/go-nownodes"
"github.com/mrz1836/go-whatsonchain"
"github.com/newrelic/go-agent/v3/newrelic"
"github.com/tonicpow/go-minercraft"
"github.com/tonicpow/go-minercraft/v2"
)

// ClientOps allow functional options to be supplied
Expand All @@ -24,15 +22,18 @@ func defaultClientOptions() *clientOptions {

// Create the default miners
bm, qm := defaultMiners()
apis, _ := minercraft.DefaultMinersAPIs()

// Set the default options
return &clientOptions{
config: &syncConfig{
httpClient: nil,
mAPI: &mAPIConfig{
broadcastMiners: bm,
queryMiners: qm,
feeUnit: DefaultFee,
minercraftConfig: &minercraftConfig{
broadcastMiners: bm,
queryMiners: qm,
minerAPIs: apis,
minercraftFeeQuotes: true,
feeUnit: DefaultFee,
},
minercraft: nil,
network: MainNet,
Expand All @@ -46,7 +47,6 @@ func defaultClientOptions() *clientOptions {

// defaultMiners will return the miners for default configuration
func defaultMiners() (broadcastMiners []*Miner, queryMiners []*Miner) {

// Set the broadcast miners
miners, _ := minercraft.DefaultMiners()

Expand Down Expand Up @@ -115,6 +115,20 @@ func WithMinercraft(client minercraft.ClientInterface) ClientOps {
}
}

// WithMAPI will specify mAPI as an API for minercraft client
func WithMAPI() ClientOps {
return func(c *clientOptions) {
c.config.minercraftConfig.apiType = minercraft.MAPI
}
}

// WithArc will specify Arc as an API for minercraft client
func WithArc() ClientOps {
return func(c *clientOptions) {
c.config.minercraftConfig.apiType = minercraft.Arc
}
}

// WithWhatsOnChain will set a custom WhatsOnChain client
func WithWhatsOnChain(client whatsonchain.ClientInterface) ClientOps {
return func(c *clientOptions) {
Expand Down Expand Up @@ -155,7 +169,7 @@ func WithWhatsOnChainAPIKey(apiKey string) ClientOps {
func WithBroadcastMiners(miners []*Miner) ClientOps {
return func(c *clientOptions) {
if len(miners) > 0 {
c.config.mAPI.broadcastMiners = miners
c.config.minercraftConfig.broadcastMiners = miners
}
}
}
Expand All @@ -164,7 +178,7 @@ func WithBroadcastMiners(miners []*Miner) ClientOps {
func WithQueryMiners(miners []*Miner) ClientOps {
return func(c *clientOptions) {
if len(miners) > 0 {
c.config.mAPI.queryMiners = miners
c.config.minercraftConfig.queryMiners = miners
}
}
}
Expand Down Expand Up @@ -233,55 +247,16 @@ func WithExcludedProviders(providers []string) ClientOps {
}
}

// WithMapiFeeQuotes will set mapiFeeQuotesEnabled flag as true
func WithMapiFeeQuotes() ClientOps {
// WithMinercraftFeeQuotes will set minercraftFeeQuotes flag as true
func WithMinercraftFeeQuotes() ClientOps {
return func(c *clientOptions) {
c.config.mAPI.mapiFeeQuotesEnabled = true
c.config.minercraftConfig.minercraftFeeQuotes = true
}
}

// WithOverridenMAPIConfig will override default config
func WithOverridenMAPIConfig(miners []*minercraft.Miner) ClientOps {
// WithMinercraftAPIs will set miners APIs
func WithMinercraftAPIs(apis []*minercraft.MinerAPIs) ClientOps {
return func(c *clientOptions) {
overrideMAPIConfig(c.config.mAPI.broadcastMiners, miners)
overrideMAPIConfig(c.config.mAPI.queryMiners, miners)
}
}

// Looks for miners by name over the mAPI config, and rewrites fields presented in a custom config
func overrideMAPIConfig(configToOverride []*Miner, customConfig []*minercraft.Miner) {
for _, miner := range customConfig {
var minerToOverride *minercraft.Miner
for _, m := range configToOverride {
if strings.EqualFold(m.Miner.Name, miner.Name) {
minerToOverride = m.Miner
break
}
}
// The miner is not in the configuration, and therefore there is nothing to override. Skip
if minerToOverride == nil {
continue
}
// Reflect values of miners. Needed to loop over all miner's fields and overwrite only some of them
// Miners are pointers in both configs, so we use reflect.ValueOf(miner).Elem()
minerToOverrideReflect := reflect.ValueOf(minerToOverride).Elem()
overrideReflect := reflect.ValueOf(miner).Elem()
// We don't override 'Name' field. Should be skipped
fieldToIgnore := overrideReflect.FieldByName("Name")

for i := 0; i < overrideReflect.NumField(); i++ {
newField := overrideReflect.Field(i)
if newField == fieldToIgnore {
continue
}
// Only non-zero fields from custom config will used as overwrite fields
if !newField.IsZero() {
name := overrideReflect.Type().Field(i).Name
fieldToOverride := minerToOverrideReflect.FieldByName(name)
if fieldToOverride.CanSet() {
fieldToOverride.Set(newField)
}
}
}
c.config.minercraftConfig.minerAPIs = apis
}
}
Loading