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

Commit

Permalink
feat(BUX-166): Decoding Merkle Path
Browse files Browse the repository at this point in the history
  • Loading branch information
kuba-4chain committed Oct 10, 2023
1 parent 28d4a14 commit 5aa1f38
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 50 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ type QueryTxResponse struct {
BlockHash string `json:"blockHash,omitempty"`
BlockHeight int64 `json:"blockHeight,omitempty"`
ExtraInfo string `json:"extraInfo,omitempty"`
MerklePath string `json:"merklePath,omitempty"`
MerklePath *bc.MerklePath `json:"merklePath,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
TxStatus TxStatus `json:"txStatus,omitempty"`
TxID string `json:"txid,omitempty"`
Expand Down Expand Up @@ -308,7 +308,7 @@ type SubmitTxResponse struct {
BlockHash string `json:"blockHash,omitempty"`
BlockHeight int64 `json:"blockHeight,omitempty"`
ExtraInfo string `json:"extraInfo,omitempty"`
MerklePath string `json:"merklePath,omitempty"`
MerklePath *bc.MerklePath `json:"merklePath,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
TxStatus TxStatus `json:"txStatus,omitempty"`
TxID string `json:"txid,omitempty"`
Expand Down
3 changes: 2 additions & 1 deletion broadcast/broadcast-client-mock/fixtures/fixtures.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
TxBlockHeightSecondary int64 = 799439
TxExtraInfo = ""
TxMerklePath = "fd123511fc17127984cea2846b6a6b3965cbd523ea92736c11022372dc922d0013ee15f577bb7d832ce6102e22a15c6e4f4e4214e246d769f04baaf458e3407f5117de8eceb78782a1f90bfaa103915dcc3aa940c7e1476398db47dc62833f3149c51593cf9eeaec36f86c871d64e849a486b97eee1ee863c18aa28e599e3b30853a8563a87441202a97f707d0614cfc024f73ba4a2848ec63e19bc5fb168e994c2c2c105b5951dee4775f20879cb63b3b525370b35200e9f91e9e0fa234dfae5cc4792d17ab08f5cb29aa97e99742dc1e48a9d7e648be33db2d75b806519d88c62a57b39ef900cf68c9d765a1a3b17895d05077ab33280fdb4c09f818d31065b1325b6eb0366c4fd48c2840c1e3a5eb64a9596ffe53bbb315b44784882fb727a98e791d4c8a0bc1d5a88fd85365afdf83da86216cf81b587ee960b5c2a4174dfb26fa2606addf5e51edea58f76cc415f21876c9fa4551891aab537f4e463f1ded910d6a27ff27531c32b4dad15360b35d4d6da348c0c311aaf412bd19a92e2253e90c67104c8f0c0167cf43032f4ffa616cb8c5c28733a3faef5afb83d3c621b252112cbf35859c7b3b4caf820ae43f7054db87da2b7bc735f41b0969b4caadd1cc78be66fb367990103b5682f33605628dcc9af941bf555c64e1c48d3d0955f2c53f7ed7611d976d3effb053eb13b85ca2bff429516aa21227c39d6b6f4106f513069a559805977dee6462d354522d6e42f30d60744bd0f08471577ea5e1f63f319261"
TxMerklePathSecondary = "fd13400f3e40f8eedd367dbe5dee632fb577e513fcc8d73cd1f6032ee6f710e95c5907d71f67fbc7027c4652a67752c45b90057f3758f6582aaad728c5ea7a29b29f53798f6ce2a931f86c84f665b31fa5247669dbf7501cba7c562c4f11a58654fc1a8a57f78cffbced9bcc18925ed46f1002f22a4f56edf29c403dc9ca5846994a89f54038e8c68b898d17e505dd1e861b1dd20ffd8736dfc3c503706ee27ec1b852cbcdb33a564c0e8f0959cf1b758bdf0164f318de57c1e674d11e5b6b0c5336a662edfeb161ced6b16916f0e4f709ba9498b3d0440dbff65ca82c39f64e3487d3d9e7e46c8251eb2e443202a19b060ddf3dcbd6414371ff0544de2370d1948ba8c5a9ef29350e6475061fa82428b1b199f7d5e7a2ad28ccba7c802a5ba38b038ec3e4710e1483440e507f7e188d35693082d441d48e4be16e5da0f6e3203b93f295334a08ead059024a0750cbc88ee77a6b3101203ff8eb17353b58f5d03568084fd22b22467e500f72fa368021c8c8f0fa559b41ec3c01c6c355a9186a7d8bda348bf5ee7035d66e8b32ed57ebfcff619e893ea9a9873bd63c8247f4467d91e16fa256fc586a56e652850ee65b69bad583c72860f30ac04f6f8d71acc4840853353b9ae3c1da33107bb631e55d0c6b474d42c95f42611a50e21ff4271350fc4868"
TxId = "680d975a403fd9ec90f613e87d17802c029d2d930df1c8373cdcdda2f536a1c0"
TxIdSecondary = "d4f4bcb8decdbc75a3dcc54580c76457ee663a28b843956733570b38f7c73b5a"
TxIdSecondary = "469dd0f63fe4b3fe972dc72d28057e931abd69d8dfc85bf6e623efa41d50ef73"
)
3 changes: 3 additions & 0 deletions broadcast/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ var ErrBroadcasterFailed = errors.New("broadcaster failed")
// ErrUnableToDecodeResponse is returned when the http response cannot be decoded.
var ErrUnableToDecodeResponse = errors.New("unable to decode response")

// ErrUnableToDecodeMerklePath is returned when merkle path from transaction response cannot be decoded.
var ErrUnableToDecodeMerklePath = errors.New("unable to decode merkle path from response")

// ErrMissingStatus is returned when the tx status is missing.
var ErrMissingStatus = errors.New("missing tx status")

Expand Down
35 changes: 31 additions & 4 deletions broadcast/internal/arc/arc_query_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package arc
import (
"context"
"errors"
"net/http"

"github.com/libsv/go-bc"

"github.com/bitcoin-sv/go-broadcast-client/broadcast"
arcutils "github.com/bitcoin-sv/go-broadcast-client/broadcast/internal/arc/utils"
Expand Down Expand Up @@ -46,15 +49,39 @@ func queryTransaction(ctx context.Context, arc *ArcClient, txHash string) (*broa
return nil, err
}

model := broadcast.QueryTxResponse{}
err = arcutils.DecodeResponseBody(resp.Body, &model)
model, err := decodeQueryResponseBody(resp, arc)
if err != nil {
return nil, err
}

model.Miner = arc.apiURL
return model, nil
}

func decodeQueryResponseBody(resp *http.Response, arc *ArcClient) (*broadcast.QueryTxResponse, error) {
base := broadcast.BaseTxResponse{}
err := arcutils.DecodeResponseBody(resp.Body, &base)
if err != nil {
return nil, err
}

var merklePath *bc.MerklePath

if base.MerklePath != "" {
merklePath, err = bc.NewMerklePathFromStr(base.MerklePath)
if err != nil {
return nil, broadcast.ErrUnableToDecodeMerklePath
}
}

model := &broadcast.QueryTxResponse{
BaseResponse: broadcast.BaseResponse{
Miner: arc.apiURL,
},
BaseTxResponse: base,
MerklePath: merklePath,
}

return &model, nil
return model, nil
}

func validateQueryTxResponse(model *broadcast.QueryTxResponse) error {
Expand Down
59 changes: 59 additions & 0 deletions broadcast/internal/arc/arc_query_tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"testing"

"github.com/libsv/go-bc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

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

func TestQueryTransaction(t *testing.T) {
Expand Down Expand Up @@ -87,3 +90,59 @@ func TestQueryTransaction(t *testing.T) {
})
}
}

func TestDecodeQueryResponseBody(t *testing.T) {
mp, _ := bc.NewMerklePathFromStr(fixtures.TxMerklePath)
testCases := []struct {
name string
httpResponse *http.Response
expectedResult *broadcast.QueryTxResponse
}{
{
name: "successful decode",
httpResponse: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf("{\"merklePath\":\"%s\"}", fixtures.TxMerklePath))),
},
expectedResult: &broadcast.QueryTxResponse{
BaseResponse: broadcast.BaseResponse{Miner: "http://example.com"},
BaseTxResponse: broadcast.BaseTxResponse{
MerklePath: fixtures.TxMerklePath,
},
MerklePath: mp,
},
},
{
name: "empty merkle path",
httpResponse: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(`
{
"merklePath": ""
}
`)),
},
expectedResult: &broadcast.QueryTxResponse{
BaseResponse: broadcast.BaseResponse{
Miner: "http://example.com",
},
MerklePath: nil,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// given
client := &ArcClient{
apiURL: "http://example.com",
}

// when
model, err := decodeQueryResponseBody(tc.httpResponse, client)

// then
assert.NoError(t, err)
assert.Equal(t, tc.expectedResult, model)
})
}
}
61 changes: 54 additions & 7 deletions broadcast/internal/arc/arc_submit_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"

"github.com/GorillaPool/go-junglebus"
"github.com/libsv/go-bc"
"github.com/libsv/go-bt/v2"

"github.com/bitcoin-sv/go-broadcast-client/broadcast"
Expand Down Expand Up @@ -72,12 +74,12 @@ func (a *ArcClient) SubmitBatchTransactions(ctx context.Context, txs []*broadcas
return nil, err
}

response := broadcast.SubmitBatchTxResponse{
response := &broadcast.SubmitBatchTxResponse{
BaseResponse: broadcast.BaseResponse{Miner: a.apiURL},
Transactions: result,
}

return &response, nil
return response, nil
}

func submitTransaction(ctx context.Context, arc *ArcClient, tx *broadcast.Transaction, opts *broadcast.TransactionOpts) (*broadcast.SubmittedTx, error) {
Expand All @@ -103,13 +105,12 @@ func submitTransaction(ctx context.Context, arc *ArcClient, tx *broadcast.Transa
return nil, arc_utils.HandleHttpError(err)
}

model := broadcast.SubmittedTx{}
err = arc_utils.DecodeResponseBody(resp.Body, &model)
model, err := decodeSubmitResponseBody(resp)
if err != nil {
return nil, err
}

return &model, nil
return model, nil
}

func submitBatchTransactions(ctx context.Context, arc *ArcClient, txs []*broadcast.Transaction, opts *broadcast.TransactionOpts) ([]*broadcast.SubmittedTx, error) {
Expand All @@ -135,8 +136,7 @@ func submitBatchTransactions(ctx context.Context, arc *ArcClient, txs []*broadca
return nil, arc_utils.HandleHttpError(err)
}

var model []*broadcast.SubmittedTx
err = arc_utils.DecodeResponseBody(resp.Body, &model)
model, err := decodeSubmitBatchResponseBody(resp)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -209,6 +209,53 @@ func appendSubmitTxHeaders(pld *httpclient.HTTPRequest, opts *broadcast.Transact
}
}

func decodeSubmitResponseBody(resp *http.Response) (*broadcast.SubmittedTx, error) {
base := broadcast.BaseSubmitTxResponse{}
err := arc_utils.DecodeResponseBody(resp.Body, &base)
if err != nil {
return nil, broadcast.ErrUnableToDecodeMerklePath
}

var merklePath *bc.MerklePath

if base.MerklePath != "" {
if merklePath, err = bc.NewMerklePathFromStr(base.MerklePath); err != nil {
return nil, broadcast.ErrUnableToDecodeMerklePath
}
}

model := &broadcast.SubmittedTx{
BaseSubmitTxResponse: base,
MerklePath: merklePath,
}

return model, nil
}

func decodeSubmitBatchResponseBody(resp *http.Response) ([]*broadcast.SubmittedTx, error) {
base := make([]broadcast.BaseSubmitTxResponse, 0)
err := arc_utils.DecodeResponseBody(resp.Body, &base)
if err != nil {
return nil, err
}

model := make([]*broadcast.SubmittedTx, 0)
for _, tx := range base {
var merklePath *bc.MerklePath
if tx.MerklePath != "" {
if merklePath, err = bc.NewMerklePathFromStr(tx.MerklePath); err != nil {
return nil, broadcast.ErrUnableToDecodeMerklePath
}
}
model = append(model, &broadcast.SubmittedTx{
BaseSubmitTxResponse: tx,
MerklePath: merklePath,
})
}

return model, nil
}

func validateBatchResponse(model []*broadcast.SubmittedTx) error {
for _, tx := range model {
if err := validateSubmitTxResponse(tx); err != nil {
Expand Down
70 changes: 64 additions & 6 deletions broadcast/internal/arc/arc_submit_tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"testing"

"github.com/libsv/go-bc"
"github.com/stretchr/testify/assert"

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

Expand Down Expand Up @@ -39,8 +42,10 @@ func TestSubmitTransaction(t *testing.T) {
expectedResult: &broadcast.SubmitTxResponse{
BaseResponse: broadcast.BaseResponse{Miner: "http://example.com"},
SubmittedTx: &broadcast.SubmittedTx{
BaseTxResponse: broadcast.BaseTxResponse{
TxStatus: broadcast.Confirmed,
BaseSubmitTxResponse: broadcast.BaseSubmitTxResponse{
BaseTxResponse: broadcast.BaseTxResponse{
TxStatus: broadcast.Confirmed,
},
},
},
},
Expand Down Expand Up @@ -177,6 +182,55 @@ func TestConvertBatchTransactions(t *testing.T) {
}
}

func TestDecodeSubmitResponseBody(t *testing.T) {
mp, _ := bc.NewMerklePathFromStr(fixtures.TxMerklePath)
testCases := []struct {
name string
httpResponse *http.Response
expectedResult *broadcast.SubmittedTx
}{
{
name: "successful decode",
httpResponse: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf("{\"merklePath\":\"%s\"}", fixtures.TxMerklePath))),
},
expectedResult: &broadcast.SubmittedTx{
BaseSubmitTxResponse: broadcast.BaseSubmitTxResponse{
BaseTxResponse: broadcast.BaseTxResponse{
MerklePath: fixtures.TxMerklePath,
},
},
MerklePath: mp,
},
},
{
name: "empty merkle path",
httpResponse: &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(`
{
"merklePath": ""
}
`)),
},
expectedResult: &broadcast.SubmittedTx{
MerklePath: nil,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// when
model, err := decodeSubmitResponseBody(tc.httpResponse)

// then
assert.NoError(t, err)
assert.Equal(t, tc.expectedResult, model)
})
}
}

func TestSubmitBatchTransactions(t *testing.T) {
testCases := []struct {
name string
Expand Down Expand Up @@ -208,13 +262,17 @@ func TestSubmitBatchTransactions(t *testing.T) {
BaseResponse: broadcast.BaseResponse{Miner: "http://example.com"},
Transactions: []*broadcast.SubmittedTx{
{
BaseTxResponse: broadcast.BaseTxResponse{
TxStatus: broadcast.Confirmed,
BaseSubmitTxResponse: broadcast.BaseSubmitTxResponse{
BaseTxResponse: broadcast.BaseTxResponse{
TxStatus: broadcast.Confirmed,
},
},
},
{
BaseTxResponse: broadcast.BaseTxResponse{
TxStatus: broadcast.Confirmed,
BaseSubmitTxResponse: broadcast.BaseSubmitTxResponse{
BaseTxResponse: broadcast.BaseTxResponse{
TxStatus: broadcast.Confirmed,
},
},
},
},
Expand Down
Loading

0 comments on commit 5aa1f38

Please sign in to comment.