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

Commit

Permalink
feat(BUX-149): fee quotes implemented (#7)
Browse files Browse the repository at this point in the history
* feat(BUX-149): fee quotes implemented

* refactor: changing best quote to be parallel
  • Loading branch information
kuba-4chain authored Aug 11, 2023
1 parent 3be1b74 commit c15d749
Show file tree
Hide file tree
Showing 18 changed files with 770 additions and 0 deletions.
7 changes: 7 additions & 0 deletions broadcast/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package broadcast

import "time"

const (
DefaultFastestQuoteTimeout = 10 * time.Second
)
2 changes: 2 additions & 0 deletions broadcast/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ var ErrMissingStatus = errors.New("missing tx status")

var ErrStrategyUnkown = errors.New("unknown strategy")

var ErrNoMinerResponse = errors.New("failed to get reponse from any miner")

type ArcError struct {
Type string `json:"type"`
Title string `json:"title"`
Expand Down
25 changes: 25 additions & 0 deletions broadcast/fee_quotes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package broadcast

type PolicyQuoteResponse struct {
Miner string `json:"miner"`
Policy PolicyResponse `json:"policy"`
Timestamp string `json:"timestamp"`
}

type FeeQuote struct {
Miner string `json:"miner"`
MiningFee MiningFeeResponse `json:"miningFee"`
Timestamp string `json:"timestamp"`
}

type PolicyResponse struct {
MaxScriptSizePolicy int64 `json:"maxscriptsizepolicy"`
MaxTxSigOpsCountPolicy int64 `json:"maxtxsigopscountspolicy"`
MaxTxSizePolicy int64 `json:"maxtxsizepolicy"`
MiningFee MiningFeeResponse `json:"miningFee"`
}

type MiningFeeResponse struct {
Bytes int64 `json:"bytes"`
Satoshis int64 `json:"satoshis"`
}
5 changes: 5 additions & 0 deletions broadcast/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ 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)
}

type PolicyQuoter interface {
GetPolicyQuote(ctx context.Context) (*PolicyQuoteResponse, error)
}

type TransactionQuerier interface {
Expand Down
21 changes: 21 additions & 0 deletions broadcast/internal/arc/arc_best_quote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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
}
85 changes: 85 additions & 0 deletions broadcast/internal/arc/arc_best_quote_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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)
})
}
}
32 changes: 32 additions & 0 deletions broadcast/internal/arc/arc_fastest_quote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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
}
86 changes: 86 additions & 0 deletions broadcast/internal/arc/arc_fastest_quote_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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)
})
}
}
21 changes: 21 additions & 0 deletions broadcast/internal/arc/arc_fee_quote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package arc

import (
"context"

"github.com/bitcoin-sv/go-broadcast-client/broadcast"
)

func (a *ArcClient) GetFeeQuote(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
}
Loading

0 comments on commit c15d749

Please sign in to comment.