Skip to content

Commit

Permalink
feat(SPV-1075) new broadcaster (#737)
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-4chain authored Oct 21, 2024
1 parent 38fdab7 commit 5fadf0f
Show file tree
Hide file tree
Showing 94 changed files with 1,356 additions and 2,593 deletions.
3 changes: 1 addition & 2 deletions actions/testabilities/fixture_spvwallet_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,7 @@ func getConfigForTests() *config.AppConfig {

cfg.DebugProfiling = false

cfg.ARC.UseFeeQuotes = false
cfg.ARC.FeeUnit = &config.FeeUnitConfig{
cfg.CustomFeeUnit = &config.FeeUnitConfig{
Satoshis: 1,
Bytes: 1000,
}
Expand Down
2 changes: 1 addition & 1 deletion actions/transactions/broadcast_callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package transactions
import (
"net/http"

chainmodels "github.com/bitcoin-sv/spv-wallet/engine/chain/models"
"github.com/bitcoin-sv/spv-wallet/engine/chain/models"
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
"github.com/bitcoin-sv/spv-wallet/server/reqctx"
"github.com/gin-gonic/gin"
Expand Down
10 changes: 4 additions & 6 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,10 @@ arc:
host: https://example.com
# token to authenticate callback calls - default callback token will be generated from the Admin Key
_token: 44a82509
# use fee quotes for transaction fee calculation
use_fee_quotes: true
# used as the fee value if 'use_fee_quotes' is set to false
fee_unit:
satoshis: 1
bytes: 1000
# custom fee unit used for calculating fees (if not set, a unit from ARC policy will be used)
_custom_fee_unit:
satoshis: 1
bytes: 1000
notifications:
enabled: false
block_headers_service:
Expand Down
6 changes: 4 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ type AppConfig struct {
DisableITC bool `json:"disable_itc" mapstructure:"disable_itc"`
// RequestLogging is flag for enabling logging in go-api-router.
RequestLogging bool `json:"request_logging" mapstructure:"request_logging"`
// CustomFeeUnit
CustomFeeUnit *FeeUnitConfig `json:"custom_fee_unit" mapstructure:"custom_fee_unit"`
}

// AuthenticationConfig is the configuration for Authentication
Expand Down Expand Up @@ -144,11 +146,9 @@ type DatastoreConfig struct {
// ARCConfig consists of blockchain nodes (Arc) configuration
type ARCConfig struct {
Callback *CallbackConfig `json:"callback" mapstructure:"callback"`
FeeUnit *FeeUnitConfig `json:"fee_unit" mapstructure:"fee_unit"`
DeploymentID string `json:"deployment_id" mapstructure:"deployment_id"`
Token string `json:"token" mapstructure:"token"`
URL string `json:"url" mapstructure:"url"`
UseFeeQuotes bool `json:"use_fee_quotes" mapstructure:"use_fee_quotes"`
}

// FeeUnitConfig reflects the utils.FeeUnit struct with proper annotations for json and mapstructure
Expand Down Expand Up @@ -241,6 +241,8 @@ type ExperimentalConfig struct {
PikeContactsEnabled bool `json:"pike_contacts_enabled" mapstructure:"pike_contacts_enabled"`
// PikePaymentEnabled is a flag for enabling Pike payment capability.
PikePaymentEnabled bool `json:"pike_payment_enabled" mapstructure:"pike_payment_enabled"`
// Use junglebus external service to fetch missing source transactions for inputs
UseJunglebus bool `json:"use_junglebus" mapstructure:"use_junglebus"`
}

// GetUserAgent will return the outgoing user agent
Expand Down
85 changes: 43 additions & 42 deletions config/config_to_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import (
"net/url"
"time"

broadcastclient "github.com/bitcoin-sv/go-broadcast-client/broadcast/broadcast-client"
"github.com/bitcoin-sv/spv-wallet/engine"
"github.com/bitcoin-sv/spv-wallet/engine/chain/models"
"github.com/bitcoin-sv/spv-wallet/engine/cluster"
"github.com/bitcoin-sv/spv-wallet/engine/datastore"
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
"github.com/bitcoin-sv/spv-wallet/engine/taskmanager"
"github.com/bitcoin-sv/spv-wallet/engine/utils"
"github.com/bitcoin-sv/spv-wallet/metrics"
"github.com/bitcoin-sv/spv-wallet/models/bsv"
"github.com/go-redis/redis/v8"
"github.com/go-resty/resty/v2"
"github.com/mrz1836/go-cachestore"
Expand Down Expand Up @@ -48,15 +49,13 @@ func (c *AppConfig) ToEngineOptions(logger zerolog.Logger) (options []engine.Cli

options = c.addNotificationOpts(options)

options = c.addARCOpts(options)
if options, err = c.addARCOpts(options); err != nil {
return nil, err
}

options = c.addBHSOpts(options)

options = c.addBroadcastClientOpts(options, logger)

if options, err = c.addCallbackOpts(options); err != nil {
return nil, err
}
options = c.addCustomFeeUnit(options)

return options, nil
}
Expand All @@ -69,6 +68,17 @@ func (c *AppConfig) addHttpClientOpts(options []engine.ClientOps) []engine.Clien
return append(options, engine.WithHTTPClient(client))
}

func (c *AppConfig) addCustomFeeUnit(options []engine.ClientOps) []engine.ClientOps {
if c.CustomFeeUnit != nil {
options = append(options, engine.WithCustomFeeUnit(bsv.FeeUnit{
Satoshis: bsv.Satoshis(c.CustomFeeUnit.Satoshis),
Bytes: c.CustomFeeUnit.Bytes,
}))
}

return options
}

func (c *AppConfig) addUserAgentOpts(options []engine.ClientOps) []engine.ClientOps {
return append(options, engine.WithUserAgent(c.GetUserAgent()))
}
Expand Down Expand Up @@ -250,44 +260,35 @@ func (c *AppConfig) addNotificationOpts(options []engine.ClientOps) []engine.Cli
return options
}

func (c *AppConfig) addARCOpts(options []engine.ClientOps) []engine.ClientOps {
return append(options, engine.WithARC(c.ARC.URL, c.ARC.Token, c.ARC.DeploymentID))
}

func (c *AppConfig) addBHSOpts(options []engine.ClientOps) []engine.ClientOps {
return append(options, engine.WithBHS(c.BHS.URL, c.BHS.AuthToken))
}

func (c *AppConfig) addBroadcastClientOpts(options []engine.ClientOps, logger zerolog.Logger) []engine.ClientOps {
bcLogger := logger.With().Str("service", "broadcast-client").Logger()

broadcastClient := broadcastclient.Builder().
WithArc(broadcastclient.ArcClientConfig{
Token: c.ARC.Token,
APIUrl: c.ARC.URL,
DeploymentID: c.ARC.DeploymentID,
}, &bcLogger).
Build()

return append(
options,
engine.WithBroadcastClient(broadcastClient),
)
}

func (c *AppConfig) addCallbackOpts(options []engine.ClientOps) ([]engine.ClientOps, error) {
if !c.ARC.Callback.Enabled {
return options, nil
func (c *AppConfig) addARCOpts(options []engine.ClientOps) ([]engine.ClientOps, error) {
arcCfg := chainmodels.ARCConfig{
URL: c.ARC.URL,
Token: c.ARC.Token,
DeploymentID: c.ARC.DeploymentID,
}

if c.ARC.Callback.Token == "" {
callbackToken, err := utils.HashAdler32(DefaultAdminXpub)
if err != nil {
return nil, spverrors.Wrapf(err, "error while generating callback token")
if c.ARC.Callback.Enabled {
var err error
if c.ARC.Callback.Token == "" {
// This also sets the token to the config reference and, it is used in the callbacktoken_middleware
// TODO: consider moving config modification to a PostLoad method and make this ToEngineOptions pure (no side effects)
if c.ARC.Callback.Token, err = utils.HashAdler32(DefaultAdminXpub); err != nil {
return nil, spverrors.Wrapf(err, "error while generating callback token")
}
}
arcCfg.Callback = &chainmodels.ARCCallbackConfig{
URL: c.ARC.Callback.Host,
Token: c.ARC.Callback.Token,
}
c.ARC.Callback.Token = callbackToken
}

options = append(options, engine.WithCallback(c.ARC.Callback.Host+BroadcastCallbackRoute, c.ARC.Callback.Token))
return options, nil
if c.ExperimentalFeatures != nil && c.ExperimentalFeatures.UseJunglebus {
arcCfg.UseJunglebus = true
}

return append(options, engine.WithARC(arcCfg)), nil
}

func (c *AppConfig) addBHSOpts(options []engine.ClientOps) []engine.ClientOps {
return append(options, engine.WithBHS(c.BHS.URL, c.BHS.AuthToken))
}
2 changes: 1 addition & 1 deletion config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func GetDefaultAppConfig() *AppConfig {
TaskManager: getTaskManagerDefault(),
Metrics: getMetricsDefaults(),
ExperimentalFeatures: getExperimentalFeaturesConfig(),
CustomFeeUnit: nil,
}
}

Expand Down Expand Up @@ -107,7 +108,6 @@ func getARCDefaults() *ARCConfig {
DeploymentID: "spv-wallet-" + depIDSufix.String(),
URL: "https://arc.taal.com",
Token: "mainnet_06770f425eb00298839a24a49cbdc02c",
UseFeeQuotes: true,
Callback: &CallbackConfig{
Enabled: false,
Host: "https://example.com",
Expand Down
4 changes: 4 additions & 0 deletions config/validate_app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,9 @@ func (c *AppConfig) Validate() error {
return err
}

if err = c.CustomFeeUnit.Validate(); err != nil {
return err
}

return nil
}
4 changes: 0 additions & 4 deletions config/validate_arc.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ func (n *ARCConfig) Validate() error {
return spverrors.Newf("invalid callback host: %s - must be a valid external url - not a localhost", n.Callback.Host)
}

if !n.UseFeeQuotes && n.FeeUnit == nil {
return spverrors.Newf("fee unit is not configured, define nodes.fee_unit or set nodes.use_fee_quotes")
}

return nil
}

Expand Down
28 changes: 0 additions & 28 deletions config/validate_arc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,32 +105,4 @@ func TestValidateArcConfig(t *testing.T) {
require.Error(t, err)
})
}

t.Run("fee unit must be set if not using fee quotes", func(t *testing.T) {
// given:
cfg := config.GetDefaultAppConfig()

cfg.ARC.UseFeeQuotes = false
cfg.ARC.FeeUnit = nil

// when:
err := cfg.Validate()

// then:
require.Error(t, err)
})

t.Run("fee unit can be not set when using fee quotes", func(t *testing.T) {
// given:
cfg := config.GetDefaultAppConfig()

cfg.ARC.UseFeeQuotes = true
cfg.ARC.FeeUnit = nil

// when:
err := cfg.Validate()

// then:
require.NoError(t, err)
})
}
18 changes: 18 additions & 0 deletions config/validate_customfee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package config

import "github.com/bitcoin-sv/spv-wallet/engine/spverrors"

// Validate validates the custom fee unit configuration
func (cf *FeeUnitConfig) Validate() error {
if cf == nil {
return nil
}

if cf.Bytes <= 0 {
return spverrors.Newf("invalid custom fee unit - bytes value is equal or less than zero: %d", cf.Bytes)
}
if cf.Satoshis < 0 {
return spverrors.Newf("invalid custom fee unit - satoshis value is less than zero: %d", cf.Satoshis)
}
return nil
}
98 changes: 98 additions & 0 deletions config/validate_customfee_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package config_test

import (
"testing"

"github.com/bitcoin-sv/spv-wallet/config"
"github.com/stretchr/testify/require"
)

func TestValidateFeeUnit(t *testing.T) {
validConfigTests := map[string]struct {
scenario func(cfg *config.AppConfig)
}{
"Not defined is valid": {
scenario: func(cfg *config.AppConfig) {
cfg.CustomFeeUnit = nil
},
},
"Standard": {
scenario: func(cfg *config.AppConfig) {
cfg.CustomFeeUnit = &config.FeeUnitConfig{
Satoshis: 1,
Bytes: 1000,
}
},
},
"Zero Satoshi is valid": {
scenario: func(cfg *config.AppConfig) {
cfg.CustomFeeUnit = &config.FeeUnitConfig{
Satoshis: 0,
Bytes: 1000,
}
},
},
}
for name, test := range validConfigTests {
t.Run(name, func(t *testing.T) {
// given:
cfg := config.GetDefaultAppConfig()

test.scenario(cfg)

// when:
err := cfg.Validate()

// then:
require.NoError(t, err)
})
}

invalidConfigTests := map[string]struct {
scenario func(cfg *config.AppConfig)
}{
"Empty is not ok": {
scenario: func(cfg *config.AppConfig) {
cfg.CustomFeeUnit = &config.FeeUnitConfig{}
},
},
"Negative satoshis": {
scenario: func(cfg *config.AppConfig) {
cfg.CustomFeeUnit = &config.FeeUnitConfig{
Satoshis: -1,
Bytes: 1000,
}
},
},
"Zero bytes": {
scenario: func(cfg *config.AppConfig) {
cfg.CustomFeeUnit = &config.FeeUnitConfig{
Satoshis: 1,
Bytes: 0,
}
},
},
"Negative bytes": {
scenario: func(cfg *config.AppConfig) {
cfg.CustomFeeUnit = &config.FeeUnitConfig{
Satoshis: 1,
Bytes: -1,
}
},
},
}
for name, test := range invalidConfigTests {
t.Run(name, func(t *testing.T) {
// given:
cfg := config.GetDefaultAppConfig()

test.scenario(cfg)

// when:
err := cfg.Validate()

// then:
require.Error(t, err)
})
}
}
4 changes: 2 additions & 2 deletions engine/action_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"time"

trx "github.com/bitcoin-sv/go-sdk/transaction"
chainmodels "github.com/bitcoin-sv/spv-wallet/engine/chain/models"
"github.com/bitcoin-sv/spv-wallet/engine/chain/models"
"github.com/bitcoin-sv/spv-wallet/engine/datastore"
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
"github.com/bitcoin-sv/spv-wallet/engine/utils"
Expand Down Expand Up @@ -105,7 +105,7 @@ func (c *Client) GetTransactionsByIDs(ctx context.Context, txIDs []string) ([]*T
// Create the conditions
conditions := generateTxIDFilterConditions(txIDs)

// Get the transactions by it's IDs
// Get the transactions by its IDs
transactions, err := getTransactions(
ctx, nil, conditions, nil,
c.DefaultModelOptions()...,
Expand Down
Loading

0 comments on commit 5fadf0f

Please sign in to comment.