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

Commit

Permalink
Merge pull request #339 from vitalibalashka/feat/minercraft-v2.0.1
Browse files Browse the repository at this point in the history
feat(BUX-51): go-minercraft v2
  • Loading branch information
mergify[bot] authored Jul 24, 2023
2 parents fe95fe7 + fd495e4 commit 6bc777f
Show file tree
Hide file tree
Showing 17 changed files with 287 additions and 173 deletions.
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

0 comments on commit 6bc777f

Please sign in to comment.