diff --git a/broadcast/config.go b/broadcast/config.go deleted file mode 100644 index cd1f4c3..0000000 --- a/broadcast/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package broadcast - -import "time" - -const ( - DefaultFastestQuoteTimeout = 10 * time.Second -) diff --git a/broadcast/interface.go b/broadcast/interface.go index a74a439..9e02e23 100644 --- a/broadcast/interface.go +++ b/broadcast/interface.go @@ -2,23 +2,14 @@ package broadcast import ( "context" - "time" ) -type BestQuoter interface { - GetBestQuote(ctx context.Context) (*FeeQuote, error) -} - -type FastestQuoter interface { - GetFastestQuote(ctx context.Context, timeout time.Duration) (*FeeQuote, error) -} - type FeeQuoter interface { - GetFeeQuote(ctx context.Context) (*FeeQuote, error) + GetFeeQuote(ctx context.Context) ([]*FeeQuote, error) } type PolicyQuoter interface { - GetPolicyQuote(ctx context.Context) (*PolicyQuoteResponse, error) + GetPolicyQuote(ctx context.Context) ([]*PolicyQuoteResponse, error) } // TransactionQuerier is the interface that wraps the QueryTransaction method. @@ -45,8 +36,6 @@ type TransactionsSubmitter interface { // Client is a grouping interface that represents the entire exposed functionality of the broadcast client. type Client interface { - BestQuoter - FastestQuoter FeeQuoter PolicyQuoter TransactionQuerier diff --git a/broadcast/internal/acceptance_tests/arc_fee_quote_test.go b/broadcast/internal/acceptance_tests/arc_fee_quote_test.go new file mode 100644 index 0000000..865bfb5 --- /dev/null +++ b/broadcast/internal/acceptance_tests/arc_fee_quote_test.go @@ -0,0 +1,149 @@ +package acceptancetests + +import ( + "context" + "errors" + "io" + "net/http" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/bitcoin-sv/go-broadcast-client/broadcast" + broadcast_client "github.com/bitcoin-sv/go-broadcast-client/broadcast/broadcast-client" + "github.com/bitcoin-sv/go-broadcast-client/broadcast/internal/arc" +) + +var firstSuccessfulFeeQuoteResponse = ` +{ + "policy": { + "maxscriptsizepolicy": 100000000, + "maxtxsigopscountspolicy": 4294967295, + "maxtxsizepolicy": 100000000, + "miningFee": { + "bytes": 1000, + "satoshis": 1 + } + }, + "timestamp": "2023-08-10T13:49:07.308687569Z" +} +` + +var secondSuccessfulFeeQuoteResponse = ` +{ + "policy": { + "maxscriptsizepolicy": 100000000, + "maxtxsigopscountspolicy": 4294967295, + "maxtxsizepolicy": 100000000, + "miningFee": { + "bytes": 1000, + "satoshis": 2 + } + }, + "timestamp": "2023-08-10T13:49:07.308687569Z" +} +` + +func TestFeeQuote(t *testing.T) { + t.Run("Should successfully query from multiple ArcClients", func(t *testing.T) { + // given + httpClientMock := &arc.MockHttpClient{} + broadcaster := broadcast_client.Builder(). + WithHttpClient(httpClientMock). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc1-api-url", Token: "arc1-token"}). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc2-api-url", Token: "arc2-token"}). + Build() + + httpResponse1 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(firstSuccessfulFeeQuoteResponse)), + } + httpResponse2 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(secondSuccessfulFeeQuoteResponse)), + } + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse1, nil). + Once() + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse2, nil). + Once() + + // when + result, err := broadcaster.GetPolicyQuote(context.Background()) + + // then + assert.NoError(t, err) + assert.NotNil(t, result) + }) + + t.Run("Should return error if all ArcClients return errors", func(t *testing.T) { + // given + httpClientMock := &arc.MockHttpClient{} + broadcaster := broadcast_client.Builder(). + WithHttpClient(httpClientMock). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc1-api-url", Token: "arc1-token"}). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc2-api-url", Token: "arc2-token"}). + Build() + + httpResponse := &http.Response{} + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse, errors.New("http error")). + Twice() + + // when + result, err := broadcaster.GetPolicyQuote(context.Background()) + + // then + assert.Error(t, err) + assert.Nil(t, result) + assert.EqualError(t, err, broadcast.ErrNoMinerResponse.Error()) + }) + + t.Run("Should successfully query from single ArcClient", func(t *testing.T) { + // given + httpClientMock := &arc.MockHttpClient{} + broadcaster := broadcast_client.Builder(). + WithHttpClient(httpClientMock). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc1-api-url", Token: "arc1-token"}). + Build() + + httpResponse1 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(firstSuccessfulFeeQuoteResponse)), + } + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse1, nil). + Once() + + // when + result, err := broadcaster.GetPolicyQuote(context.Background()) + + // then + assert.NoError(t, err) + assert.NotNil(t, result) + }) + + t.Run("Should return error if single ArcClient returns error", func(t *testing.T) { + // given + httpClientMock := &arc.MockHttpClient{} + httpResponse := &http.Response{} + broadcaster := broadcast_client.Builder(). + WithHttpClient(httpClientMock). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc1-api-url", Token: "arc1-token"}). + Build() + + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse, errors.New("http error")). + Once() + + // when + result, err := broadcaster.GetPolicyQuote(context.Background()) + + // then + assert.Error(t, err) + assert.Nil(t, result) + }) +} diff --git a/broadcast/internal/acceptance_tests/arc_policy_quote_test.go b/broadcast/internal/acceptance_tests/arc_policy_quote_test.go new file mode 100644 index 0000000..b303f4d --- /dev/null +++ b/broadcast/internal/acceptance_tests/arc_policy_quote_test.go @@ -0,0 +1,149 @@ +package acceptancetests + +import ( + "context" + "errors" + "io" + "net/http" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/bitcoin-sv/go-broadcast-client/broadcast" + broadcast_client "github.com/bitcoin-sv/go-broadcast-client/broadcast/broadcast-client" + "github.com/bitcoin-sv/go-broadcast-client/broadcast/internal/arc" +) + +var firstSuccessfulPolicyResponse = ` +{ + "policy": { + "maxscriptsizepolicy": 100000000, + "maxtxsigopscountspolicy": 4294967295, + "maxtxsizepolicy": 100000000, + "miningFee": { + "bytes": 1000, + "satoshis": 1 + } + }, + "timestamp": "2023-08-10T13:49:07.308687569Z" +} +` + +var secondSuccessfulPolicyResponse = ` +{ + "policy": { + "maxscriptsizepolicy": 100000000, + "maxtxsigopscountspolicy": 4294967295, + "maxtxsizepolicy": 100000000, + "miningFee": { + "bytes": 1000, + "satoshis": 2 + } + }, + "timestamp": "2023-08-10T13:49:07.308687569Z" +} +` + +func TestPolicyQuote(t *testing.T) { + t.Run("Should successfully query from multiple ArcClients", func(t *testing.T) { + // given + httpClientMock := &arc.MockHttpClient{} + broadcaster := broadcast_client.Builder(). + WithHttpClient(httpClientMock). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc1-api-url", Token: "arc1-token"}). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc2-api-url", Token: "arc2-token"}). + Build() + + httpResponse1 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(firstSuccessfulPolicyResponse)), + } + httpResponse2 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(secondSuccessfulPolicyResponse)), + } + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse1, nil). + Once() + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse2, nil). + Once() + + // when + result, err := broadcaster.GetPolicyQuote(context.Background()) + + // then + assert.NoError(t, err) + assert.NotNil(t, result) + }) + + t.Run("Should return error if all ArcClients return errors", func(t *testing.T) { + // given + httpClientMock := &arc.MockHttpClient{} + broadcaster := broadcast_client.Builder(). + WithHttpClient(httpClientMock). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc1-api-url", Token: "arc1-token"}). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc2-api-url", Token: "arc2-token"}). + Build() + + httpResponse := &http.Response{} + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse, errors.New("http error")). + Twice() + + // when + result, err := broadcaster.GetPolicyQuote(context.Background()) + + // then + assert.Error(t, err) + assert.Nil(t, result) + assert.EqualError(t, err, broadcast.ErrNoMinerResponse.Error()) + }) + + t.Run("Should successfully query from single ArcClient", func(t *testing.T) { + // given + httpClientMock := &arc.MockHttpClient{} + broadcaster := broadcast_client.Builder(). + WithHttpClient(httpClientMock). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc1-api-url", Token: "arc1-token"}). + Build() + + httpResponse1 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(firstSuccessfulPolicyResponse)), + } + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse1, nil). + Once() + + // when + result, err := broadcaster.GetPolicyQuote(context.Background()) + + // then + assert.NoError(t, err) + assert.NotNil(t, result) + }) + + t.Run("Should return error if single ArcClient returns error", func(t *testing.T) { + // given + httpClientMock := &arc.MockHttpClient{} + httpResponse := &http.Response{} + broadcaster := broadcast_client.Builder(). + WithHttpClient(httpClientMock). + WithArc(broadcast_client.ArcClientConfig{APIUrl: "http://arc1-api-url", Token: "arc1-token"}). + Build() + + httpClientMock.On("DoRequest", mock.Anything, mock.Anything). + Return(httpResponse, errors.New("http error")). + Once() + + // when + result, err := broadcaster.GetPolicyQuote(context.Background()) + + // then + assert.Error(t, err) + assert.Nil(t, result) + }) +} diff --git a/broadcast/internal/arc/arc_best_quote.go b/broadcast/internal/arc/arc_best_quote.go deleted file mode 100644 index ea81ac9..0000000 --- a/broadcast/internal/arc/arc_best_quote.go +++ /dev/null @@ -1,21 +0,0 @@ -package arc - -import ( - "context" - - "github.com/bitcoin-sv/go-broadcast-client/broadcast" -) - -func (a *ArcClient) GetBestQuote(ctx context.Context) (*broadcast.FeeQuote, error) { - policyQuote, err := a.GetPolicyQuote(ctx) - if err != nil { - return nil, err - } - - feeQuote := &broadcast.FeeQuote{ - Miner: a.apiURL, - MiningFee: policyQuote.Policy.MiningFee, - Timestamp: policyQuote.Timestamp, - } - return feeQuote, nil -} diff --git a/broadcast/internal/arc/arc_best_quote_test.go b/broadcast/internal/arc/arc_best_quote_test.go deleted file mode 100644 index a394538..0000000 --- a/broadcast/internal/arc/arc_best_quote_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package arc - -import ( - "bytes" - "context" - "errors" - "io" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/bitcoin-sv/go-broadcast-client/broadcast" -) - -func TestBestQuote(t *testing.T) { - testCases := []struct { - name string - httpResponse *http.Response - httpError error - expectedResult *broadcast.FeeQuote - expectedError error - }{ - { - name: "successful request", - httpResponse: &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(` - { - "policy": { - "maxscriptsizepolicy": 100000000, - "maxtxsigopscountspolicy": 4294967295, - "maxtxsizepolicy": 100000000, - "miningFee": { - "bytes": 1000, - "satoshis": 1 - } - }, - "timestamp": "2023-08-10T13:49:07.308687569Z" - } - `)), - }, - expectedResult: &broadcast.FeeQuote{ - Miner: "http://example.com", - MiningFee: broadcast.MiningFeeResponse{ - Bytes: 1000, - Satoshis: 1, - }, - Timestamp: "2023-08-10T13:49:07.308687569Z", - }, - }, - { - name: "error in HTTP request", - httpError: errors.New("some error"), - expectedError: errors.New("some error"), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // given - mockHttpClient := new(MockHttpClient) - - mockHttpClient.On("DoRequest", context.Background(), mock.Anything). - Return(tc.httpResponse, tc.httpError).Once() - - client := &ArcClient{ - HTTPClient: mockHttpClient, - apiURL: "http://example.com", - token: "someToken", - } - - // when - result, err := client.GetBestQuote(context.Background()) - - // then - assert.Equal(t, tc.expectedResult, result) - assert.Equal(t, tc.expectedError, err) - - // assert Expectations on the mock - mockHttpClient.AssertExpectations(t) - }) - } -} diff --git a/broadcast/internal/arc/arc_fastest_quote.go b/broadcast/internal/arc/arc_fastest_quote.go deleted file mode 100644 index 4afa619..0000000 --- a/broadcast/internal/arc/arc_fastest_quote.go +++ /dev/null @@ -1,32 +0,0 @@ -package arc - -import ( - "context" - "time" - - "github.com/bitcoin-sv/go-broadcast-client/broadcast" -) - -func (a *ArcClient) GetFastestQuote( - ctx context.Context, - timeout time.Duration, -) (*broadcast.FeeQuote, error) { - if timeout.Seconds() == 0 { - timeout = broadcast.DefaultFastestQuoteTimeout - } - - ctxWithTimeout, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - - policyQuote, err := a.GetPolicyQuote(ctxWithTimeout) - if err != nil { - return nil, err - } - - feeQuote := &broadcast.FeeQuote{ - Miner: a.apiURL, - MiningFee: policyQuote.Policy.MiningFee, - Timestamp: policyQuote.Timestamp, - } - return feeQuote, nil -} diff --git a/broadcast/internal/arc/arc_fastest_quote_test.go b/broadcast/internal/arc/arc_fastest_quote_test.go deleted file mode 100644 index 9f852cf..0000000 --- a/broadcast/internal/arc/arc_fastest_quote_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package arc - -import ( - "bytes" - "context" - "errors" - "io" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/bitcoin-sv/go-broadcast-client/broadcast" -) - -func TestFastestQuote(t *testing.T) { - testCases := []struct { - name string - httpResponse *http.Response - httpError error - expectedResult *broadcast.FeeQuote - expectedError error - }{ - { - name: "successful request", - httpResponse: &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(` - { - "policy": { - "maxscriptsizepolicy": 100000000, - "maxtxsigopscountspolicy": 4294967295, - "maxtxsizepolicy": 100000000, - "miningFee": { - "bytes": 1000, - "satoshis": 1 - } - }, - "timestamp": "2023-08-10T13:49:07.308687569Z" - } - `)), - }, - expectedResult: &broadcast.FeeQuote{ - Miner: "http://example.com", - MiningFee: broadcast.MiningFeeResponse{ - Bytes: 1000, - Satoshis: 1, - }, - Timestamp: "2023-08-10T13:49:07.308687569Z", - }, - }, - { - name: "error in HTTP request", - httpError: errors.New("some error"), - expectedError: errors.New("some error"), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // given - mockHttpClient := new(MockHttpClient) - - mockHttpClient.On("DoRequest", mock.Anything, mock.Anything). - Return(tc.httpResponse, tc.httpError).Once() - - client := &ArcClient{ - HTTPClient: mockHttpClient, - apiURL: "http://example.com", - token: "someToken", - } - - // when - result, err := client.GetFastestQuote(context.Background(), time.Second) - - // then - assert.Equal(t, tc.expectedResult, result) - assert.Equal(t, tc.expectedError, err) - - // assert Expectations on the mock - mockHttpClient.AssertExpectations(t) - }) - } -} diff --git a/broadcast/internal/arc/arc_fee_quote.go b/broadcast/internal/arc/arc_fee_quote.go index 1e4a661..2218f36 100644 --- a/broadcast/internal/arc/arc_fee_quote.go +++ b/broadcast/internal/arc/arc_fee_quote.go @@ -6,16 +6,19 @@ import ( "github.com/bitcoin-sv/go-broadcast-client/broadcast" ) -func (a *ArcClient) GetFeeQuote(ctx context.Context) (*broadcast.FeeQuote, error) { - policyQuote, err := a.GetPolicyQuote(ctx) +func (a *ArcClient) GetFeeQuote(ctx context.Context) ([]*broadcast.FeeQuote, error) { + policyQuotes, err := a.GetPolicyQuote(ctx) if err != nil { return nil, err } feeQuote := &broadcast.FeeQuote{ Miner: a.apiURL, - MiningFee: policyQuote.Policy.MiningFee, - Timestamp: policyQuote.Timestamp, + MiningFee: policyQuotes[0].Policy.MiningFee, + Timestamp: policyQuotes[0].Timestamp, } - return feeQuote, nil + + feeQuotes := []*broadcast.FeeQuote{feeQuote} + + return feeQuotes, nil } diff --git a/broadcast/internal/arc/arc_fee_quote_test.go b/broadcast/internal/arc/arc_fee_quote_test.go index 34b3593..ac6c639 100644 --- a/broadcast/internal/arc/arc_fee_quote_test.go +++ b/broadcast/internal/arc/arc_fee_quote_test.go @@ -19,7 +19,7 @@ func TestFeeQuote(t *testing.T) { name string httpResponse *http.Response httpError error - expectedResult *broadcast.FeeQuote + expectedResult []*broadcast.FeeQuote expectedError error }{ { @@ -41,13 +41,15 @@ func TestFeeQuote(t *testing.T) { } `)), }, - expectedResult: &broadcast.FeeQuote{ - Miner: "http://example.com", - MiningFee: broadcast.MiningFeeResponse{ - Bytes: 1000, - Satoshis: 1, + expectedResult: []*broadcast.FeeQuote{ + { + Miner: "http://example.com", + MiningFee: broadcast.MiningFeeResponse{ + Bytes: 1000, + Satoshis: 1, + }, + Timestamp: "2023-08-10T13:49:07.308687569Z", }, - Timestamp: "2023-08-10T13:49:07.308687569Z", }, }, { diff --git a/broadcast/internal/arc/arc_policy_quote.go b/broadcast/internal/arc/arc_policy_quote.go index 91df1f4..22c9913 100644 --- a/broadcast/internal/arc/arc_policy_quote.go +++ b/broadcast/internal/arc/arc_policy_quote.go @@ -8,7 +8,7 @@ import ( "github.com/bitcoin-sv/go-broadcast-client/broadcast/internal/httpclient" ) -func (a *ArcClient) GetPolicyQuote(ctx context.Context) (*broadcast.PolicyQuoteResponse, error) { +func (a *ArcClient) GetPolicyQuote(ctx context.Context) ([]*broadcast.PolicyQuoteResponse, error) { if a == nil { return nil, broadcast.ErrClientUndefined } @@ -20,7 +20,9 @@ func (a *ArcClient) GetPolicyQuote(ctx context.Context) (*broadcast.PolicyQuoteR model.Miner = a.apiURL - return model, nil + models := []*broadcast.PolicyQuoteResponse{model} + + return models, nil } func getPolicyQuote(ctx context.Context, arc *ArcClient) (*broadcast.PolicyQuoteResponse, error) { diff --git a/broadcast/internal/arc/arc_policy_quote_test.go b/broadcast/internal/arc/arc_policy_quote_test.go index c2ac32e..5328525 100644 --- a/broadcast/internal/arc/arc_policy_quote_test.go +++ b/broadcast/internal/arc/arc_policy_quote_test.go @@ -19,7 +19,7 @@ func TestPolicyQuote(t *testing.T) { name string httpResponse *http.Response httpError error - expectedResult *broadcast.PolicyQuoteResponse + expectedResult []*broadcast.PolicyQuoteResponse expectedError error }{ { @@ -41,18 +41,20 @@ func TestPolicyQuote(t *testing.T) { } `)), }, - expectedResult: &broadcast.PolicyQuoteResponse{ - Miner: "http://example.com", - Policy: broadcast.PolicyResponse{ - MaxScriptSizePolicy: 100000000, - MaxTxSigOpsCountPolicy: 4294967295, - MaxTxSizePolicy: 100000000, - MiningFee: broadcast.MiningFeeResponse{ - Bytes: 1000, - Satoshis: 1, + expectedResult: []*broadcast.PolicyQuoteResponse{ + { + Miner: "http://example.com", + Policy: broadcast.PolicyResponse{ + MaxScriptSizePolicy: 100000000, + MaxTxSigOpsCountPolicy: 4294967295, + MaxTxSizePolicy: 100000000, + MiningFee: broadcast.MiningFeeResponse{ + Bytes: 1000, + Satoshis: 1, + }, }, + Timestamp: "2023-08-10T13:49:07.308687569Z", }, - Timestamp: "2023-08-10T13:49:07.308687569Z", }, }, { diff --git a/broadcast/internal/composite/broadcaster.go b/broadcast/internal/composite/broadcaster.go index 09823e1..624c4f0 100644 --- a/broadcast/internal/composite/broadcaster.go +++ b/broadcast/internal/composite/broadcaster.go @@ -3,8 +3,6 @@ package composite import ( "context" "fmt" - "sync" - "time" "github.com/bitcoin-sv/go-broadcast-client/broadcast" ) @@ -33,126 +31,46 @@ func NewBroadcaster(strategy Strategy, factories ...BroadcastFactory) broadcast. } } -func (c *compositeBroadcaster) GetPolicyQuote(ctx context.Context) (*broadcast.PolicyQuoteResponse, error) { - executionFuncs := make([]executionFunc, len(c.broadcasters)) - for i, broadcaster := range c.broadcasters { - executionFuncs[i] = func(ctx context.Context) (Result, error) { - return broadcaster.GetPolicyQuote(ctx) - } - } - - result, err := c.strategy.Execute(ctx, executionFuncs) - if err != nil { - return nil, err - } - - policyQuoteResponse, ok := result.(*broadcast.PolicyQuoteResponse) - if !ok { - return nil, fmt.Errorf("unexpected result type: %T", result) - } - - return policyQuoteResponse, nil -} - -func (c *compositeBroadcaster) GetFeeQuote(ctx context.Context) (*broadcast.FeeQuote, error) { - executionFuncs := make([]executionFunc, len(c.broadcasters)) - for i, broadcaster := range c.broadcasters { - executionFuncs[i] = func(ctx context.Context) (Result, error) { - return broadcaster.GetFeeQuote(ctx) - } - } - - result, err := c.strategy.Execute(ctx, executionFuncs) - if err != nil { - return nil, err - } - - feeQuote, ok := result.(*broadcast.FeeQuote) - if !ok { - return nil, fmt.Errorf("unexpected result type: %T", result) - } - - return feeQuote, nil -} - -// GetBestQuote: ... -func (c *compositeBroadcaster) GetBestQuote(ctx context.Context) (*broadcast.FeeQuote, error) { - fees := make(chan *broadcast.FeeQuote, len(c.broadcasters)) - var wg sync.WaitGroup +func (c *compositeBroadcaster) GetPolicyQuote( + ctx context.Context, +) ([]*broadcast.PolicyQuoteResponse, error) { + var policyQuotes []*broadcast.PolicyQuoteResponse for _, broadcaster := range c.broadcasters { - wg.Add(1) - go func(ctx context.Context, b broadcast.Client) { - defer wg.Done() - feeQuote, err := b.GetFeeQuote(ctx) - if err == nil { - fees <- feeQuote - } - }(ctx, broadcaster) - } - - wg.Wait() - close(fees) - - var bestQuote *broadcast.FeeQuote = nil - for fee := range fees { - if bestQuote == nil { - bestQuote = fee - } else { - feePer1000Bytes := (1000 / fee.MiningFee.Bytes) * fee.MiningFee.Satoshis - bestFeePer1000Bytes := (1000 / bestQuote.MiningFee.Bytes) * bestQuote.MiningFee.Satoshis - - if feePer1000Bytes < bestFeePer1000Bytes { - bestQuote = fee - } + singlePolicy, err := broadcaster.GetPolicyQuote(ctx) + if err == nil { + policyQuotes = append(policyQuotes, singlePolicy[0]) } } - if bestQuote == nil { + if policyQuotes == nil { return nil, broadcast.ErrNoMinerResponse } - return bestQuote, nil + return policyQuotes, nil } -// GetFastestQuote: ... -func (c *compositeBroadcaster) GetFastestQuote(ctx context.Context, timeout time.Duration) (*broadcast.FeeQuote, error) { - if timeout.Seconds() == 0 { - timeout = broadcast.DefaultFastestQuoteTimeout - } +func (c *compositeBroadcaster) GetFeeQuote(ctx context.Context) ([]*broadcast.FeeQuote, error) { + var feeQuotes []*broadcast.FeeQuote - ctxWithTimeout, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - - feeChannel := make(chan *broadcast.FeeQuote, len(c.broadcasters)) - - var wg sync.WaitGroup for _, broadcaster := range c.broadcasters { - wg.Add(1) - go func(ctxWithTimeout context.Context, broadcaster broadcast.Client) { - defer wg.Done() - feeQuote, err := broadcaster.GetFeeQuote(ctxWithTimeout) - if err == nil { - feeChannel <- feeQuote - } - }(ctxWithTimeout, broadcaster) + singleFeeQuote, err := broadcaster.GetFeeQuote(ctx) + if err == nil { + feeQuotes = append(feeQuotes, singleFeeQuote[0]) + } } - go func() { - wg.Wait() - close(feeChannel) - }() - - fastQuote := <-feeChannel - - if fastQuote == nil { + if feeQuotes == nil { return nil, broadcast.ErrNoMinerResponse } - return fastQuote, nil + return feeQuotes, nil } -func (c *compositeBroadcaster) QueryTransaction(ctx context.Context, txID string) (*broadcast.QueryTxResponse, error) { +func (c *compositeBroadcaster) QueryTransaction( + ctx context.Context, + txID string, +) (*broadcast.QueryTxResponse, error) { executionFuncs := make([]executionFunc, len(c.broadcasters)) for i, broadcaster := range c.broadcasters { executionFuncs[i] = func(ctx context.Context) (Result, error) { @@ -173,7 +91,10 @@ func (c *compositeBroadcaster) QueryTransaction(ctx context.Context, txID string return queryTxResponse, nil } -func (c *compositeBroadcaster) SubmitTransaction(ctx context.Context, tx *broadcast.Transaction) (*broadcast.SubmitTxResponse, error) { +func (c *compositeBroadcaster) SubmitTransaction( + ctx context.Context, + tx *broadcast.Transaction, +) (*broadcast.SubmitTxResponse, error) { executionFuncs := make([]executionFunc, len(c.broadcasters)) for i, broadcaster := range c.broadcasters { executionFuncs[i] = func(ctx context.Context) (Result, error) { @@ -194,7 +115,10 @@ func (c *compositeBroadcaster) SubmitTransaction(ctx context.Context, tx *broadc return submitTxResponse, nil } -func (c *compositeBroadcaster) SubmitBatchTransactions(ctx context.Context, txs []*broadcast.Transaction) ([]*broadcast.SubmitTxResponse, error) { +func (c *compositeBroadcaster) SubmitBatchTransactions( + ctx context.Context, + txs []*broadcast.Transaction, +) ([]*broadcast.SubmitTxResponse, error) { executionFuncs := make([]executionFunc, len(c.broadcasters)) for i, broadcaster := range c.broadcasters { executionFuncs[i] = func(ctx context.Context) (Result, error) { diff --git a/examples/get_best_quote/get_best_quote.go b/examples/get_best_quote/get_best_quote.go deleted file mode 100644 index 68f3fc1..0000000 --- a/examples/get_best_quote/get_best_quote.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "context" - "log" - - "github.com/bitcoin-sv/go-broadcast-client/broadcast" - broadcast_client "github.com/bitcoin-sv/go-broadcast-client/broadcast/broadcast-client" -) - -func main() { - token := "" - apiURL := "https://tapi.taal.com/arc" - - taalCfg := broadcast_client.ArcClientConfig{ - Token: token, - APIUrl: apiURL, - } - - gorillaCfg := broadcast_client.ArcClientConfig{ - Token: "", - APIUrl: "https://arc.gorillapool.io", - } - - client := broadcast_client.Builder(). - WithArc(taalCfg). - WithArc(gorillaCfg). - Build() - - bestQuote, err := client.GetBestQuote(context.Background()) - if err != nil { - log.Fatalf("error: %s", err.Error()) - } - - broadcast.PrettyPrint("Best Quote", bestQuote) -} diff --git a/examples/get_fastest_quote/get_fastest_quote.go b/examples/get_fastest_quote/get_fastest_quote.go deleted file mode 100644 index ed87ff4..0000000 --- a/examples/get_fastest_quote/get_fastest_quote.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "context" - "log" - "time" - - "github.com/bitcoin-sv/go-broadcast-client/broadcast" - broadcast_client "github.com/bitcoin-sv/go-broadcast-client/broadcast/broadcast-client" -) - -func main() { - taalCfg := broadcast_client.ArcClientConfig{ - Token: "", - APIUrl: "https://tapi.taal.com/arc", - } - - gorillaCfg := broadcast_client.ArcClientConfig{ - Token: "", - APIUrl: "https://arc.gorillapool.io", - } - - client := broadcast_client.Builder(). - WithArc(gorillaCfg). - WithArc(taalCfg). - Build() - - bestQuote, err := client.GetFastestQuote(context.Background(), time.Second) - if err != nil { - log.Fatalf("error: %s", err.Error()) - } - - broadcast.PrettyPrint("Best Quote", bestQuote) -} diff --git a/examples/get_fee_quote/get_fee_quote.go b/examples/get_fee_quote/get_fee_quote.go index 2878d2a..1e346f1 100644 --- a/examples/get_fee_quote/get_fee_quote.go +++ b/examples/get_fee_quote/get_fee_quote.go @@ -9,22 +9,25 @@ import ( ) func main() { - token := "" - apiURL := "https://tapi.taal.com/arc" + taalCfg := broadcast_client.ArcClientConfig{ + Token: "", + APIUrl: "https://tapi.taal.com/arc", + } - cfg := broadcast_client.ArcClientConfig{ - Token: token, - APIUrl: apiURL, + gorillaCfg := broadcast_client.ArcClientConfig{ + Token: "", + APIUrl: "https://arc.gorillapool.io", } client := broadcast_client.Builder(). - WithArc(cfg). + WithArc(taalCfg). + WithArc(gorillaCfg). Build() - feeQuote, err := client.GetFeeQuote(context.Background()) + feeQuotes, err := client.GetFeeQuote(context.Background()) if err != nil { log.Fatalf("error: %s", err.Error()) } - broadcast.PrettyPrint("Fee Quote", feeQuote) + broadcast.PrettyPrint("Fee Quotes", feeQuotes) } diff --git a/examples/get_policy_quote/get_policy_quote.go b/examples/get_policy_quote/get_policy_quote.go index 6989c6e..88132bc 100644 --- a/examples/get_policy_quote/get_policy_quote.go +++ b/examples/get_policy_quote/get_policy_quote.go @@ -9,22 +9,25 @@ import ( ) func main() { - token := "" - apiURL := "https://tapi.taal.com/arc" + taalCfg := broadcast_client.ArcClientConfig{ + Token: "", + APIUrl: "https://tapi.taal.com/arc", + } - cfg := broadcast_client.ArcClientConfig{ - Token: token, - APIUrl: apiURL, + gorillaCfg := broadcast_client.ArcClientConfig{ + Token: "", + APIUrl: "https://arc.gorillapool.io", } client := broadcast_client.Builder(). - WithArc(cfg). + WithArc(taalCfg). + WithArc(gorillaCfg). Build() - policyQuote, err := client.GetPolicyQuote(context.Background()) + policyQuotes, err := client.GetPolicyQuote(context.Background()) if err != nil { log.Fatalf("error: %s", err.Error()) } - broadcast.PrettyPrint("Policy", policyQuote) + broadcast.PrettyPrint("Policies", policyQuotes) }