Skip to content

Commit

Permalink
feat(SPV-1398): change outline response to be prepared for BEEF and R…
Browse files Browse the repository at this point in the history
…aw formatting of transaction.
  • Loading branch information
dorzepowski committed Jan 29, 2025
1 parent 924bdb9 commit d3b0e2a
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package annotatedtx
import (
"maps"

"github.com/bitcoin-sv/spv-wallet/engine/v2/bsv"
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction"
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction/outlines"
model "github.com/bitcoin-sv/spv-wallet/models/transaction"
Expand All @@ -14,7 +15,7 @@ type Request model.AnnotatedTransaction
// ToEngine converts a request model to the engine model.
func (req Request) ToEngine() *outlines.Transaction {
return &outlines.Transaction{
BEEF: req.BEEF,
Hex: bsv.TxHex(req.Hex),
Annotations: transaction.Annotations{
Outputs: maps.Collect(func(yield func(int, *transaction.OutputAnnotation) bool) {
if req.Annotations == nil || len(req.Annotations.Outputs) == 0 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (

// ToResponse converts a transaction outline to a response model.
func ToResponse(tx *outlines.Transaction) (*model.AnnotatedTransaction, error) {
res := &model.AnnotatedTransaction{}
res := &model.AnnotatedTransaction{
Format: tx.Hex.Format(),
}
err := mapstructure.Decode(tx, res)
if err != nil {
return nil, spverrors.ErrCannotMapFromModel.Wrap(err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mapping
import (
"maps"

"github.com/bitcoin-sv/spv-wallet/engine/v2/bsv"
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction"
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction/outlines"
"github.com/bitcoin-sv/spv-wallet/models/request"
Expand All @@ -11,7 +12,7 @@ import (
// TransactionOutline maps request's AnnotatedTransaction to outlines.Transaction.
func TransactionOutline(req *request.AnnotatedTransaction) *outlines.Transaction {
return &outlines.Transaction{
BEEF: req.BEEF,
Hex: bsv.TxHex(req.Hex),
Annotations: transaction.Annotations{
Outputs: maps.Collect(func(yield func(int, *transaction.OutputAnnotation) bool) {
if req.Annotations == nil || len(req.Annotations.Outputs) == 0 {
Expand Down
2 changes: 1 addition & 1 deletion actions/v2/transactions/outlines.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func transactionOutlines(c *gin.Context, userCtx *reqctx.UserContext) {
return
}

txOutline, err := reqctx.Engine(c).TransactionOutlinesService().Create(c, spec)
txOutline, err := reqctx.Engine(c).TransactionOutlinesService().CreateBEEF(c, spec)
if err != nil {
spverrors.ErrorResponse(c, err, logger)
return
Expand Down
18 changes: 12 additions & 6 deletions actions/v2/transactions/outlines_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ func TestPOSTTransactionOutlines(t *testing.T) {
]
}`,
response: `{
"beef": "0100beef000100000000000100000000000000000e006a04736f6d65012004646174610000000000",
"hex": "0100beef000100000000000100000000000000000e006a04736f6d65012004646174610000000000",
"format": "BEEF",
"annotations": {
"outputs": {
"0": {
Expand All @@ -48,7 +49,8 @@ func TestPOSTTransactionOutlines(t *testing.T) {
]
}`,
response: `{
"beef": "0100beef000100000000000100000000000000000e006a04736f6d65012004646174610000000000",
"hex": "0100beef000100000000000100000000000000000e006a04736f6d65012004646174610000000000",
"format": "BEEF",
"annotations": {
"outputs": {
"0": {
Expand All @@ -69,7 +71,8 @@ func TestPOSTTransactionOutlines(t *testing.T) {
]
}`,
response: `{
"beef": "0100beef000100000000000100000000000000000e006a04736f6d65012004646174610000000000",
"hex": "0100beef000100000000000100000000000000000e006a04736f6d65012004646174610000000000",
"format": "BEEF",
"annotations": {
"outputs": {
"0": {
Expand All @@ -90,7 +93,8 @@ func TestPOSTTransactionOutlines(t *testing.T) {
]
}`, fixtures.RecipientExternal.DefaultPaymail()),
response: fmt.Sprintf(`{
"beef": "0100beef0001000000000001e8030000000000001976a9143e2d1d795f8acaa7957045cc59376177eb04a3c588ac0000000000",
"hex": "0100beef0001000000000001e8030000000000001976a9143e2d1d795f8acaa7957045cc59376177eb04a3c588ac0000000000",
"format": "BEEF",
"annotations": {
"outputs": {
"0": {
Expand Down Expand Up @@ -122,7 +126,8 @@ func TestPOSTTransactionOutlines(t *testing.T) {
fixtures.Sender.DefaultPaymail(),
),
response: fmt.Sprintf(`{
"beef": "0100beef0001000000000001e8030000000000001976a9143e2d1d795f8acaa7957045cc59376177eb04a3c588ac0000000000",
"hex": "0100beef0001000000000001e8030000000000001976a9143e2d1d795f8acaa7957045cc59376177eb04a3c588ac0000000000",
"format": "BEEF",
"annotations": {
"outputs": {
"0": {
Expand Down Expand Up @@ -158,7 +163,8 @@ func TestPOSTTransactionOutlines(t *testing.T) {
fixtures.Sender.DefaultPaymail(),
),
response: fmt.Sprintf(`{
"beef": "0100beef0001000000000002e8030000000000001976a9143e2d1d795f8acaa7957045cc59376177eb04a3c588ac00000000000000000e006a04736f6d65012004646174610000000000",
"hex": "0100beef0001000000000002e8030000000000001976a9143e2d1d795f8acaa7957045cc59376177eb04a3c588ac00000000000000000e006a04736f6d65012004646174610000000000",
"format": "BEEF",
"annotations": {
"outputs": {
"0": {
Expand Down
8 changes: 4 additions & 4 deletions actions/v2/transactions/outlines_record_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestOutlinesRecordOpReturn(t *testing.T) {

// and:
request := `{
"beef": "` + txSpec.BEEF() + `",
"hex": "` + txSpec.BEEF() + `",
"annotations": {
"outputs": {
"0": {
Expand Down Expand Up @@ -132,7 +132,7 @@ func TestOutlinesRecordOpReturnErrorCases(t *testing.T) {
}{
"RecordTransactionOutline for not signed transaction": {
request: `{
"beef": "` + givenUnsignedTX.BEEF() + `"
"hex": "` + givenUnsignedTX.BEEF() + `"
}`,
expectHttpCode: 400,
expectedErr: apierror.ExpectedJSON("error-transaction-validation", "transaction validation failed"),
Expand Down Expand Up @@ -160,7 +160,7 @@ func TestOutlinesRecordOpReturnErrorCases(t *testing.T) {
},
"no-op_return output annotated as data": {
request: `{
"beef": "` + givenTxWithP2PKHOutput.BEEF() + `",
"hex": "` + givenTxWithP2PKHOutput.BEEF() + `",
"annotations": {
"outputs": {
"0": {
Expand Down Expand Up @@ -207,7 +207,7 @@ func TestOutlinesRecordOpReturnOnBroadcastError(t *testing.T) {
// and:
txSpec := givenTXWithOpReturn(t)
request := `{
"beef": "` + txSpec.BEEF() + `",
"hex": "` + txSpec.BEEF() + `",
"annotations": {
"outputs": {
"0": {
Expand Down
37 changes: 37 additions & 0 deletions engine/v2/bsv/transaction_hex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package bsv

import (
"strings"

"github.com/bitcoin-sv/go-sdk/transaction"
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
)

// TxHex is a hex representation of a transaction.
type TxHex string

// IsBEEF checks if the transaction hex is a BEEF hex.
func (h TxHex) IsBEEF() bool {
return strings.HasPrefix(string(h), "0100BEEF") || strings.HasPrefix(string(h), "0100beef")
}

// IsRawTx checks if the transaction hex is a raw transaction hex.
func (h TxHex) IsRawTx() bool {
return !h.IsBEEF()
}

// ToBEEFTransaction converts the transaction hex to a BEEF transaction.
func (h TxHex) ToBEEFTransaction() (*transaction.Transaction, error) {
if !h.IsBEEF() {
return nil, spverrors.Newf("transaction hex is not a BEEF hex")
}
return transaction.NewTransactionFromBEEFHex(string(h))

Check failure on line 28 in engine/v2/bsv/transaction_hex.go

View workflow job for this annotation

GitHub Actions / Lint for errors

error returned from external package is unwrapped: sig: func github.com/bitcoin-sv/go-sdk/transaction.NewTransactionFromBEEFHex(beefHex string) (*github.com/bitcoin-sv/go-sdk/transaction.Transaction, error) (wrapcheck)

Check failure on line 28 in engine/v2/bsv/transaction_hex.go

View workflow job for this annotation

GitHub Actions / Lint for errors

error returned from external package is unwrapped: sig: func github.com/bitcoin-sv/go-sdk/transaction.NewTransactionFromBEEFHex(beefHex string) (*github.com/bitcoin-sv/go-sdk/transaction.Transaction, error) (wrapcheck)
}

// Format returns the name of the format of the transaction hex.
func (h TxHex) Format() string {
if h.IsBEEF() {
return "BEEF"
}
return "RAW"
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func TestCreateOpReturnTransactionOutline(t *testing.T) {
}

// when:
tx, err := service.Create(context.Background(), spec)
tx, err := service.CreateBEEF(context.Background(), spec)

// then:
thenTx := then.Created(tx).WithNoError(err).WithParseableBEEFHex()
Expand Down Expand Up @@ -130,7 +130,7 @@ func TestCreateOpReturnTransactionOutline(t *testing.T) {
}

// when:
tx, err := service.Create(context.Background(), spec)
tx, err := service.CreateBEEF(context.Background(), spec)

// then:
then.Created(tx).WithError(err).ThatIs(test.expectedError)
Expand Down
45 changes: 43 additions & 2 deletions engine/v2/transaction/outlines/create_outline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/bitcoin-sv/spv-wallet/models"
)

func TestCreateTransactionOutlineError(t *testing.T) {
func TestCreateBEEFTransactionOutlineError(t *testing.T) {
errorTests := map[string]struct {
spec *outlines.TransactionSpec
expectedError models.SPVError
Expand Down Expand Up @@ -44,7 +44,48 @@ func TestCreateTransactionOutlineError(t *testing.T) {
service := given.NewTransactionOutlinesService()

// when:
tx, err := service.Create(context.Background(), test.spec)
tx, err := service.CreateBEEF(context.Background(), test.spec)

// then:
then.Created(tx).WithError(err).ThatIs(test.expectedError)
})
}
}

func TestCreateRawTransactionOutlineError(t *testing.T) {
errorTests := map[string]struct {
spec *outlines.TransactionSpec
expectedError models.SPVError
}{
"return error for nil as transaction spec": {
spec: nil,
expectedError: txerrors.ErrTxOutlineSpecificationRequired,
},
"return error for transaction spec without xPub Id": {
spec: &outlines.TransactionSpec{},
expectedError: txerrors.ErrTxOutlineSpecificationUserIDRequired,
},
"return error for no outputs in transaction spec": {
spec: &outlines.TransactionSpec{UserID: fixtures.Sender.ID()},
expectedError: txerrors.ErrTxOutlineRequiresAtLeastOneOutput,
},
"return error for empty output list in transaction spec": {
spec: &outlines.TransactionSpec{
UserID: fixtures.Sender.ID(),
Outputs: outlines.NewOutputsSpecs(),
},
expectedError: txerrors.ErrTxOutlineRequiresAtLeastOneOutput,
},
}
for name, test := range errorTests {
t.Run(name, func(t *testing.T) {
given, then := testabilities.New(t)

// given:
service := given.NewTransactionOutlinesService()

// when:
tx, err := service.CreateBEEF(context.Background(), test.spec)

// then:
then.Created(tx).WithError(err).ThatIs(test.expectedError)
Expand Down
10 changes: 5 additions & 5 deletions engine/v2/transaction/outlines/create_paymail_outline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestCreatePaymailTransactionOutline(t *testing.T) {
}

// when:
tx, err := service.Create(context.Background(), spec)
tx, err := service.CreateBEEF(context.Background(), spec)

// then:
thenTx := then.Created(tx).WithNoError(err).WithParseableBEEFHex()
Expand Down Expand Up @@ -83,7 +83,7 @@ func TestCreatePaymailTransactionOutline(t *testing.T) {
}

// when:
tx, err := service.Create(context.Background(), spec)
tx, err := service.CreateBEEF(context.Background(), spec)

// then:
thenTx := then.Created(tx).WithNoError(err).WithParseableBEEFHex()
Expand Down Expand Up @@ -128,7 +128,7 @@ func TestCreatePaymailTransactionOutline(t *testing.T) {
}

// when:
tx, err := service.Create(context.Background(), spec)
tx, err := service.CreateBEEF(context.Background(), spec)

// then:
then.Created(tx).WithNoError(err).WithParseableBEEFHex().
Expand Down Expand Up @@ -276,7 +276,7 @@ func TestCreatePaymailTransactionOutline(t *testing.T) {
}

// when:
tx, err := service.Create(context.Background(), spec)
tx, err := service.CreateBEEF(context.Background(), spec)

// then:
then.Created(tx).WithError(err).ThatIs(test.expectedError)
Expand Down Expand Up @@ -344,7 +344,7 @@ func TestCreatePaymailTransactionOutline(t *testing.T) {
}

// when:
tx, err := service.Create(context.Background(), spec)
tx, err := service.CreateBEEF(context.Background(), spec)

// then:
then.Created(tx).WithError(err).ThatIs(test.expectedError)
Expand Down
5 changes: 3 additions & 2 deletions engine/v2/transaction/outlines/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package outlines
import (
"context"

"github.com/bitcoin-sv/spv-wallet/engine/v2/bsv"
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction"
)

Expand All @@ -14,11 +15,11 @@ type PaymailAddressService interface {

// Service is a service for creating transaction outlines.
type Service interface {
Create(ctx context.Context, spec *TransactionSpec) (*Transaction, error)
CreateBEEF(ctx context.Context, spec *TransactionSpec) (*Transaction, error)
}

// Transaction represents a transaction outline.
type Transaction struct {
BEEF string
Hex bsv.TxHex
Annotations transaction.Annotations
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (a *assertion) ThatIs(expectedError error) {
a.assert.ErrorIs(a.err, expectedError)
}

// WithNoError checks if there was no error and result is not nil. It also checks if BEEF hex is parseable.
// WithNoError checks if there was no error and result is not nil.
func (a *assertion) WithNoError(err error) SuccessfullyCreatedTransactionOutlineAssertion {
a.t.Helper()
a.require.NoError(err, "Creation of transaction outline has finished with error")
Expand All @@ -75,11 +75,11 @@ func (a *assertion) WithNoError(err error) SuccessfullyCreatedTransactionOutline

func (a *assertion) WithParseableBEEFHex() WithParseableBEEFTransactionOutlineAssertion {
a.t.Helper()
a.t.Logf("BEEF: %s", a.txOutline.BEEF)
a.t.Logf("Hex: %s", a.txOutline.Hex)

var err error
a.tx, err = sdk.NewTransactionFromBEEFHex(a.txOutline.BEEF)
a.require.NoErrorf(err, "Invalid BEEF hex: %s", a.txOutline.BEEF)
a.tx, err = a.txOutline.Hex.ToBEEFTransaction()
a.require.NoErrorf(err, "Invalid Hex hex: %s", a.txOutline.Hex)
return a
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/bitcoin-sv/spv-wallet/engine/paymail"
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
"github.com/bitcoin-sv/spv-wallet/engine/v2/bsv"
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction/errors"
"github.com/rs/zerolog"
)
Expand Down Expand Up @@ -32,8 +33,8 @@ func NewService(paymailService paymail.ServiceClient, paymailAddressService Paym
}
}

// Create creates a new transaction outline based on specification.
func (s *service) Create(ctx context.Context, spec *TransactionSpec) (*Transaction, error) {
// CreateBEEF creates a new transaction outline based on specification.
func (s *service) CreateBEEF(ctx context.Context, spec *TransactionSpec) (*Transaction, error) {
if spec == nil {
return nil, txerrors.ErrTxOutlineSpecificationRequired
}
Expand Down Expand Up @@ -61,7 +62,7 @@ func (s *service) Create(ctx context.Context, spec *TransactionSpec) (*Transacti
}

return &Transaction{
BEEF: beef,
Hex: bsv.TxHex(beef),
Annotations: annotations,
}, nil
}
Loading

0 comments on commit d3b0e2a

Please sign in to comment.