Skip to content

Commit

Permalink
refactor(SPV-1404): adjust tx outline to type42 changes.
Browse files Browse the repository at this point in the history
  • Loading branch information
dorzepowski committed Jan 22, 2025
1 parent d4c9702 commit bfde677
Show file tree
Hide file tree
Showing 37 changed files with 466 additions and 374 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
"github.com/bitcoin-sv/spv-wallet/engine/transaction/outlines"
"github.com/bitcoin-sv/spv-wallet/engine/transaction/outlines/outputs"
"github.com/bitcoin-sv/spv-wallet/models/request"
"github.com/bitcoin-sv/spv-wallet/models/request/opreturn"
paymailreq "github.com/bitcoin-sv/spv-wallet/models/request/paymail"
Expand All @@ -17,9 +16,9 @@ import (
type Request request.TransactionSpecification

// ToEngine converts a transaction outline request model to the engine model.
func (tx Request) ToEngine(xPubID string) (*outlines.TransactionSpec, error) {
func (tx Request) ToEngine(userID string) (*outlines.TransactionSpec, error) {
spec := &outlines.TransactionSpec{
XPubID: xPubID,
UserID: userID,
}
config := mapstructure.DecoderConfig{
DecodeHook: outputsHookFunc(),
Expand All @@ -40,7 +39,7 @@ func (tx Request) ToEngine(xPubID string) (*outlines.TransactionSpec, error) {

func outputsHookFunc() mapstructure.DecodeHookFunc {
return func(_ reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
specs := outputs.NewSpecifications()
specs := outlines.NewOutputsSpec()
reqOutputs, ok := data.([]request.Output)
if !ok {
return data, nil
Expand All @@ -60,13 +59,13 @@ func outputsHookFunc() mapstructure.DecodeHookFunc {
}
}

func outputSpecFromRequest(req request.Output) (outputs.Spec, error) {
func outputSpecFromRequest(req request.Output) (outlines.OutputSpec, error) {
switch o := req.(type) {
case opreturn.Output:
out := outputs.OpReturn(o)
out := outlines.OpReturn(o)
return &out, nil
case paymailreq.Output:
out := outputs.Paymail(o)
out := outlines.Paymail(o)
return &out, nil
default:
return nil, errors.New("unsupported output type")
Expand Down
8 changes: 7 additions & 1 deletion actions/transactions/outlines.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ func transactionOutlines(c *gin.Context, userCtx *reqctx.UserContext) {
return
}

spec, err := outline.Request(requestBody).ToEngine(userCtx.GetXPubID())
userID, err := userCtx.ShouldGetUserID()
if err != nil {
spverrors.ErrorResponse(c, err, logger)
return
}

spec, err := outline.Request(requestBody).ToEngine(userID)
if err != nil {
spverrors.ErrorResponse(c, err, logger)
return
Expand Down
39 changes: 39 additions & 0 deletions actions/transactions/outlines_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,45 @@ func TestPOSTTransactionOutlines(t *testing.T) {
fixtures.Sender.DefaultPaymail(),
),
},
"create transaction outline for paymail and data": {
request: fmt.Sprintf(`{
"outputs": [
{
"type": "paymail",
"to": "%s",
"satoshis": 1000,
"from": "%s"
},
{
"type": "op_return",
"data": [ "some", " ", "data" ]
}
]
}`, fixtures.RecipientExternal.DefaultPaymail(),
fixtures.Sender.DefaultPaymail(),
),
response: fmt.Sprintf(`{
"beef": "0100beef0001000000000002e8030000000000001976a9143e2d1d795f8acaa7957045cc59376177eb04a3c588ac00000000000000000e006a04736f6d65012004646174610000000000",
"annotations": {
"outputs": {
"0": {
"bucket": "bsv",
"paymail": {
"receiver": "%s",
"reference": "z0bac4ec-6f15-42de-9ef4-e60bfdabf4f7",
"sender": "%s"
}
},
"1": {
"bucket": "data"
}
}
}
}`,
fixtures.RecipientExternal.DefaultPaymail(),
fixtures.Sender.DefaultPaymail(),
),
},
}
for name, test := range successTestCases {
t.Run(name, func(t *testing.T) {
Expand Down
31 changes: 1 addition & 30 deletions engine/client_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,36 +166,7 @@ func (c *Client) loadPaymailAddressService() error {
if c.options.paymailAddressService != nil {
return nil
}
c.options.paymailAddressService = paymailaddress.NewService(
func(ctx context.Context, address string) (string, error) {
paymailAddress, err := c.GetPaymailAddress(ctx, address)
if err != nil {
return "", err
}
return paymailAddress.XpubID, nil
},
func(ctx context.Context, xPubId string) ([]string, error) {
page := &datastore.QueryParams{
PageSize: 10,
OrderByField: createdAtField,
SortDirection: datastore.SortAsc,
}

conditions := map[string]interface{}{
xPubIDField: xPubId,
}

addresses, err := c.GetPaymailAddresses(ctx, nil, conditions, page)
if err != nil {
return nil, err
}
result := make([]string, 0, len(addresses))
for _, address := range addresses {
result = append(result, address.String())
}
return result, nil
},
)
c.options.paymailAddressService = paymailaddress.NewService(c.Repositories().Paymails)
return nil
}

Expand Down
17 changes: 17 additions & 0 deletions engine/database/repository/paymails.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,20 @@ func (p *Paymails) Get(ctx context.Context, alias, domain string) (*database.Pay

return &paymail, nil
}

// GetDefault returns a default paymail for user.
func (p *Paymails) GetDefault(ctx context.Context, userID string) (*database.Paymail, error) {
var paymail database.Paymail
if err := p.db.
WithContext(ctx).
Where("user_id = ?", userID).
Order("created_at ASC").
First(&paymail).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}

return &paymail, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import "context"

// Service is a component that provides methods for working with paymail address.
type Service interface {
HasPaymailAddress(ctx context.Context, xPubID string, address string) (bool, error)
GetDefaultPaymailAddress(ctx context.Context, xPubID string) (string, error)
HasPaymailAddress(ctx context.Context, userID string, address string) (bool, error)
GetDefaultPaymailAddress(ctx context.Context, userID string) (string, error)
}
45 changes: 19 additions & 26 deletions engine/paymailaddress/paymail_address_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,44 @@ package paymailaddress
import (
"context"

"github.com/bitcoin-sv/go-paymail"
"github.com/bitcoin-sv/spv-wallet/engine/database/repository"
"github.com/bitcoin-sv/spv-wallet/engine/paymailaddress/paerrors"
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
)

type service struct {
// INFO: this is the first step to slowly move paymail address to a separate package.
getXPubIDByPaymailAddress func(ctx context.Context, paymailAddress string) (string, error)
getPaymailAddressesByXPubIDOrderByCreatedAsc func(ctx context.Context, xPubId string) ([]string, error)
repo *repository.Paymails
}

// NewService creates a new paymail address service.
func NewService(
getXPubIDByPaymailAddress func(ctx context.Context, paymailAddress string) (string, error),
getPaymailAddressesByXPubIDOrderByCreatedAsc func(ctx context.Context, xPubId string) ([]string, error),
) Service {
if getXPubIDByPaymailAddress == nil {
panic("getXPubIDByPaymailAddress is required to create paymail address service")
}
if getPaymailAddressesByXPubIDOrderByCreatedAsc == nil {
panic("getPaymailAddressesByXPubIDOrderByCreatedAsc is required to create paymail address service")
}

return &service{
getXPubIDByPaymailAddress: getXPubIDByPaymailAddress,
getPaymailAddressesByXPubIDOrderByCreatedAsc: getPaymailAddressesByXPubIDOrderByCreatedAsc,
}
func NewService(repo *repository.Paymails) Service {
return &service{repo: repo}
}

// HasPaymailAddress checks if the given address belongs to a given xPubId.
func (s *service) HasPaymailAddress(ctx context.Context, xPubID string, address string) (bool, error) {
paymailXpubID, err := s.getXPubIDByPaymailAddress(ctx, address)
// HasPaymailAddress checks if the given address belongs to a given User.
func (s *service) HasPaymailAddress(ctx context.Context, userID string, address string) (bool, error) {
alias, domain, _ := paymail.SanitizePaymail(address)
pm, err := s.repo.Get(ctx, alias, domain)
if err != nil {
return false, err

Check failure on line 26 in engine/paymailaddress/paymail_address_service.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/spv-wallet/engine/database/repository.Paymails).Get(ctx context.Context, alias string, domain string) (*github.com/bitcoin-sv/spv-wallet/engine/database.Paymail, error) (wrapcheck)

Check failure on line 26 in engine/paymailaddress/paymail_address_service.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/spv-wallet/engine/database/repository.Paymails).Get(ctx context.Context, alias string, domain string) (*github.com/bitcoin-sv/spv-wallet/engine/database.Paymail, error) (wrapcheck)
}
return paymailXpubID == xPubID, nil

if pm == nil {
return false, nil
}

return pm.UserID == userID, nil
}

// GetDefaultPaymailAddress returns the default paymail address for the given xPubId.
func (s *service) GetDefaultPaymailAddress(ctx context.Context, xPubID string) (string, error) {
addresses, err := s.getPaymailAddressesByXPubIDOrderByCreatedAsc(ctx, xPubID)
pm, err := s.repo.GetDefault(ctx, xPubID)
if err != nil {
return "", spverrors.ErrInternal.Wrap(err)
}
if len(addresses) == 0 {
} else if pm == nil {
return "", paerrors.ErrNoDefaultPaymailAddress
}
return addresses[0], nil

return pm.Alias + "@" + pm.Domain, nil
}

This file was deleted.

3 changes: 3 additions & 0 deletions engine/tester/fixtures/users_fixtures.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ var (

// DefaultPaymail returns the default paymail of this user.
func (f *User) DefaultPaymail() string {
if len(f.Paymails) == 0 {
return ""
}
return f.Paymails[0]
}

Expand Down
14 changes: 11 additions & 3 deletions engine/transaction/annotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@ import (

// Annotations represents a transaction metadata that will be used by server to properly handle given transaction.
type Annotations struct {
Inputs InputAnnotations
Outputs OutputsAnnotations
}

// InputAnnotations represents the metadata for chosen inputs. The key is the index of the input.
type InputAnnotations map[int]*InputAnnotation

// InputAnnotation represents the metadata for the input.
type InputAnnotation struct {
}

// OutputsAnnotations represents the metadata for chosen outputs. The key is the index of the output.
type OutputsAnnotations map[int]*OutputAnnotation

// OutputAnnotation represents the metadata for the output.
type OutputAnnotation struct {
// What type of bucket should this output be stored in.
Expand All @@ -21,9 +32,6 @@ type OutputAnnotation struct {
// PaymailAnnotation is the metadata for the paymail output.
type PaymailAnnotation transaction.PaymailAnnotation

// OutputsAnnotations represents the metadata for chosen outputs. The key is the index of the output.
type OutputsAnnotations map[int]*OutputAnnotation

// NewDataOutputAnnotation constructs a new OutputAnnotation for the data output.
func NewDataOutputAnnotation() *OutputAnnotation {
return &OutputAnnotation{
Expand Down
7 changes: 5 additions & 2 deletions engine/transaction/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ var (
// ErrTxOutlineSpecificationRequired is returned when a transaction outline is created with no specification.
ErrTxOutlineSpecificationRequired = models.SPVError{Code: "tx-spec-spec-required", Message: "transaction outline requires a specification", StatusCode: 400}

// ErrTxOutlineSpecificationXPubIDRequired is returned when a transaction outline is created without xPubID.
ErrTxOutlineSpecificationXPubIDRequired = models.SPVError{Code: "tx-spec-spec-xpub-id-required", Message: "cannot create transaction outline without knowledge about xPubID", StatusCode: 500}
// ErrTxOutlineSpecificationUserIDRequired is returned when a transaction outline is created without UserID.
ErrTxOutlineSpecificationUserIDRequired = models.SPVError{Code: "tx-spec-spec-user-id-required", Message: "cannot create transaction outline without knowledge about userID", StatusCode: 500}

// ErrTxOutlineRequiresAtLeastOneOutput is returned when a transaction outline is created with no outputs.
ErrTxOutlineRequiresAtLeastOneOutput = models.SPVError{Code: "tx-spec-output-required", Message: "transaction outline requires at least one output", StatusCode: 400}
Expand All @@ -24,6 +24,9 @@ var (
// ErrTxOutlineSenderPaymailAddressNoDefault is when it is not possible to determine the default address for the sender.
ErrTxOutlineSenderPaymailAddressNoDefault = models.SPVError{Code: "error-tx-spec-paymail-address-no-default", Message: "cannot choose paymail address of the sender", StatusCode: 400}

// ErrTxOutlineInsufficientFunds is returned when user has not enough BSV in UTXOs to fulfil the transaction.

Check failure on line 27 in engine/transaction/errors/errors.go

View workflow job for this annotation

GitHub Actions / Lint for style lint errors

`fulfil` is a misspelling of `fulfill` (misspell)

Check failure on line 27 in engine/transaction/errors/errors.go

View workflow job for this annotation

GitHub Actions / Lint for style lint errors

`fulfil` is a misspelling of `fulfill` (misspell)
ErrTxOutlineInsufficientFunds = models.SPVError{Code: "tx-outline-not-enough-funds", Message: "not enough funds to make the transaction", StatusCode: 422}

// ErrFailedToDecodeHex is returned when hex decoding fails.
ErrFailedToDecodeHex = models.SPVError{Code: "failed-to-decode-hex", Message: "failed to decode hex", StatusCode: 400}

Expand Down
Loading

0 comments on commit bfde677

Please sign in to comment.