Skip to content

Commit

Permalink
Refactored fee response parsing, using go-bt v2 fee
Browse files Browse the repository at this point in the history
  • Loading branch information
mrz1836 committed Feb 4, 2022
1 parent 1be8a65 commit 59568b7
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 37 deletions.
41 changes: 21 additions & 20 deletions best_quote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
)

Expand Down Expand Up @@ -194,8 +195,8 @@ func TestClient_BestQuote(t *testing.T) {

// Create a req
response, err := client.BestQuote(context.Background(), FeeCategoryMining, FeeTypeData)
assert.NoError(t, err)
assert.NotNil(t, response)
require.NoError(t, err)
require.NotNil(t, response)

// Check returned values
assert.Equal(t, testEncoding, response.Encoding)
Expand All @@ -210,60 +211,60 @@ func TestClient_BestQuote(t *testing.T) {

client := newTestClient(&mockHTTPError{})
response, err := client.BestQuote(context.Background(), FeeCategoryMining, FeeTypeData)
assert.Error(t, err)
assert.Nil(t, response)
require.Error(t, err)
require.Nil(t, response)
})

t.Run("bad request", func(t *testing.T) {
defer goleak.VerifyNone(t)

client := newTestClient(&mockHTTPBadRequest{})
response, err := client.BestQuote(context.Background(), FeeCategoryMining, FeeTypeData)
assert.Error(t, err)
assert.Nil(t, response)
require.Error(t, err)
require.Nil(t, response)
})

t.Run("invalid JSON", func(t *testing.T) {
defer goleak.VerifyNone(t)

client := newTestClient(&mockHTTPInvalidJSON{})
response, err := client.BestQuote(context.Background(), FeeCategoryMining, FeeTypeData)
assert.Error(t, err)
assert.Nil(t, response)
require.Error(t, err)
require.Nil(t, response)
})

t.Run("invalid category", func(t *testing.T) {
defer goleak.VerifyNone(t)

client := newTestClient(&mockHTTPValidBestQuote{})
response, err := client.BestQuote(context.Background(), "invalid", FeeTypeData)
assert.Error(t, err)
assert.Nil(t, response)
require.Error(t, err)
require.Nil(t, response)

// Create a req
response, err = client.BestQuote(context.Background(), FeeCategoryMining, "invalid")
assert.Error(t, err)
assert.Nil(t, response)
require.Error(t, err)
require.Nil(t, response)
})

t.Run("better rate", func(t *testing.T) {
defer goleak.VerifyNone(t)

client := newTestClient(&mockHTTPBetterRate{})
response, err := client.BestQuote(context.Background(), FeeCategoryRelay, FeeTypeData)
assert.NoError(t, err)
assert.NotNil(t, response)
require.NoError(t, err)
require.NotNil(t, response)

// Check that we got fees
assert.Equal(t, 2, len(response.Quote.Fees))

var fee uint64
fee, err = response.Quote.CalculateFee(FeeCategoryRelay, FeeTypeData, 1000)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, uint64(100), fee)

fee, err = response.Quote.CalculateFee(FeeCategoryMining, FeeTypeData, 1000)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, uint64(500), fee)
})

Expand All @@ -272,8 +273,8 @@ func TestClient_BestQuote(t *testing.T) {

client := newTestClient(&mockHTTPBadRate{})
response, err := client.BestQuote(context.Background(), FeeCategoryRelay, FeeTypeData)
assert.Error(t, err)
assert.Nil(t, response)
require.Error(t, err)
require.Nil(t, response)
})

t.Run("best quote - two failed", func(t *testing.T) {
Expand All @@ -284,8 +285,8 @@ func TestClient_BestQuote(t *testing.T) {

// Create a req
response, err := client.BestQuote(context.Background(), FeeCategoryMining, FeeTypeData)
assert.NoError(t, err)
assert.NotNil(t, response)
require.NoError(t, err)
require.NotNil(t, response)

// Check returned values
assert.Equal(t, testEncoding, response.Encoding)
Expand Down
2 changes: 1 addition & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "time"
const (

// version is the current package version
version = "v0.6.2"
version = "v0.7.0"

// defaultUserAgent is the default user agent for all requests
defaultUserAgent string = "go-minercraft: " + version
Expand Down
4 changes: 2 additions & 2 deletions fastest_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ func (c *Client) fetchFastestQuote(ctx context.Context, timeout time.Duration) *
var wg sync.WaitGroup
for _, miner := range c.miners {
wg.Add(1)
go func(ctx context.Context, wg *sync.WaitGroup, client *Client, miner *Miner) {
go func(ctx2 context.Context, wg *sync.WaitGroup, client *Client, miner *Miner) {
defer wg.Done()
res := getQuote(ctx, client, miner, routeFeeQuote)
res := getQuote(ctx2, client, miner, routeFeeQuote)
if res.Response.Error == nil {
resultsChannel <- res
}
Expand Down
20 changes: 20 additions & 0 deletions fastest_quote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,26 @@ func TestClient_FastestQuote(t *testing.T) {
// log.Println(response.Quote.Fees[0].MiningFee)
})

t.Run("valid quote - no timeout", func(t *testing.T) {

defer goleak.VerifyNone(t)

// Create a client
client := newTestClient(&mockHTTPValidFastestQuoteSlow{})

// Create a req
response, err := client.FastestQuote(context.Background(), 0)
assert.NoError(t, err)
assert.NotNil(t, response)

// Check returned values
assert.Equal(t, testEncoding, response.Encoding)
assert.Equal(t, testMimeType, response.MimeType)

// Check that we got fees
assert.Equal(t, 2, len(response.Quote.Fees))
})

t.Run("http error", func(t *testing.T) {

defer goleak.VerifyNone(t)
Expand Down
98 changes: 86 additions & 12 deletions fee_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"net/http"
"strings"

"github.com/libsv/go-bt"
"github.com/libsv/go-bt/v2"
)

const (
Expand Down Expand Up @@ -86,16 +86,47 @@ Example FeeQuoteResponse.Payload (unmarshalled):

// FeePayload is the unmarshalled version of the payload envelope
type FeePayload struct {
APIVersion string `json:"apiVersion"`
Timestamp string `json:"timestamp"`
ExpirationTime string `json:"expiryTime"`
MinerID string `json:"minerId"`
CurrentHighestBlockHash string `json:"currentHighestBlockHash"`
CurrentHighestBlockHeight uint64 `json:"currentHighestBlockHeight"`
MinerReputation interface{} `json:"minerReputation"` // Not sure what this value is
Fees []*bt.Fee `json:"fees"`
feePayloadFields
Fees []*bt.Fee `json:"fees"`
}

type (

// rawFeePayload is the unmarshalled version of the payload envelope
rawFeePayload struct {
feePayloadFields
Fees []*feeObj `json:"fees"`
}

// feePayloadFields are the same fields in both payloads
feePayloadFields struct {
APIVersion string `json:"apiVersion"`
Timestamp string `json:"timestamp"`
ExpirationTime string `json:"expiryTime"`
MinerID string `json:"minerId"`
CurrentHighestBlockHash string `json:"currentHighestBlockHash"`
CurrentHighestBlockHeight uint64 `json:"currentHighestBlockHeight"`
MinerReputation interface{} `json:"minerReputation"` // Not sure what this value is
}

// feeUnit displays the amount of Satoshis needed
// for a specific amount of Bytes in a transaction
// see https://github.com/bitcoin-sv-specs/brfc-merchantapi#expanded-payload-1
feeUnit struct {
Satoshis int `json:"satoshis"` // Fee in satoshis of the amount of Bytes
Bytes int `json:"bytes"` // Number of bytes that the Fee covers
}

// feeObj displays the MiningFee as well as the RelayFee for a specific
// FeeType, for example 'standard' or 'data'
// see https://github.com/bitcoin-sv-specs/brfc-merchantapi#expanded-payload-1
feeObj struct {
FeeType string `json:"feeType"` // standard || data
MiningFee feeUnit `json:"miningFee"`
RelayFee feeUnit `json:"relayFee"` // Fee for retaining Tx in secondary mempool
}
)

// CalculateFee will return the fee for the given txBytes
// Type: "FeeTypeData" or "FeeTypeStandard"
// Category: "FeeCategoryMining" or "FeeCategoryRelay"
Expand All @@ -116,7 +147,7 @@ func (f *FeePayload) CalculateFee(feeCategory, feeType string, txBytes uint64) (
for _, fee := range f.Fees {

// Detect the type (data or standard)
if fee.FeeType != feeType {
if string(fee.FeeType) != feeType {
continue
}

Expand Down Expand Up @@ -146,7 +177,7 @@ func (f *FeePayload) GetFee(feeType string) *bt.Fee {

// Loop the fees for the given type
for index, fee := range f.Fees {
if fee.FeeType == feeType {
if string(fee.FeeType) == feeType {
return f.Fees[index]
}
}
Expand Down Expand Up @@ -205,11 +236,54 @@ func (i *internalResult) parseFeeQuote() (response FeeQuoteResponse, err error)

// If we have a valid payload
if len(response.Payload) > 0 {
err = json.Unmarshal([]byte(response.Payload), &response.Quote)

// Create a raw payload shim
p := new(rawFeePayload)
if err = json.Unmarshal([]byte(response.Payload), &p); err != nil {
return
}
if response.Quote == nil {
response.Quote = new(FeePayload)
}

// Create the response payload
rawPayloadIntoQuote(p, response.Quote)
}
return
}

// rawPayloadIntoQuote will convert the raw parsed payload into a final quote payload
func rawPayloadIntoQuote(payload *rawFeePayload, quote *FeePayload) {

// Set the fields from the raw payload into the quote
quote.MinerID = payload.MinerID
quote.APIVersion = payload.APIVersion
quote.Timestamp = payload.Timestamp
quote.ExpirationTime = payload.ExpirationTime
quote.CurrentHighestBlockHash = payload.CurrentHighestBlockHash
quote.CurrentHighestBlockHeight = payload.CurrentHighestBlockHeight
quote.MinerReputation = payload.MinerReputation

// Convert the mAPI fees into go-bt fees
for _, f := range payload.Fees {
t := bt.FeeTypeStandard
if f.FeeType == FeeTypeData {
t = bt.FeeTypeData
}
quote.Fees = append(quote.Fees, &bt.Fee{
FeeType: t,
MiningFee: bt.FeeUnit{
Satoshis: f.MiningFee.Satoshis,
Bytes: f.MiningFee.Bytes,
},
RelayFee: bt.FeeUnit{
Satoshis: f.RelayFee.Satoshis,
Bytes: f.RelayFee.Bytes,
},
})
}
}

// getQuote will fire the HTTP request to retrieve the fee/policy quote
func getQuote(ctx context.Context, client *Client, miner *Miner, route string) (result *internalResult) {
result = &internalResult{Miner: miner}
Expand Down
5 changes: 3 additions & 2 deletions fee_quote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"testing"

"github.com/libsv/go-bt/v2"
"github.com/stretchr/testify/assert"
"go.uber.org/goleak"
)
Expand Down Expand Up @@ -535,12 +536,12 @@ func TestFeePayload_GetFee(t *testing.T) {
// Standard
fee := response.Quote.GetFee(FeeTypeStandard)
assert.NotNil(t, fee)
assert.Equal(t, "standard", fee.FeeType)
assert.Equal(t, bt.FeeTypeStandard, fee.FeeType)

// Data
fee = response.Quote.GetFee(FeeTypeData)
assert.NotNil(t, fee)
assert.Equal(t, "data", fee.FeeType)
assert.Equal(t, bt.FeeTypeData, fee.FeeType)
})

t.Run("missing fee type", func(t *testing.T) {
Expand Down

0 comments on commit 59568b7

Please sign in to comment.