Skip to content

Commit

Permalink
chore(SPV-846): refactor examples to show extended errors (#249)
Browse files Browse the repository at this point in the history
Co-authored-by: Krzysztof Tomecki <[email protected]>
  • Loading branch information
pawellewandowski98 and chris-4chain authored Jul 23, 2024
1 parent 44c5921 commit c1170bf
Show file tree
Hide file tree
Showing 15 changed files with 76 additions and 46 deletions.
22 changes: 17 additions & 5 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,30 @@ import (
var ErrAdminKey = models.SPVError{Message: "an admin key must be set to be able to create an xpub", StatusCode: 401, Code: "error-unauthorized-admin-key-not-set"}

// ErrMissingXpriv is when xpriv is missing
var ErrMissingXpriv = models.SPVError{Message: "xpriv missing", StatusCode: 401, Code: "error-unauthorized-xpriv-missing"}
var ErrMissingXpriv = models.SPVError{Message: "xpriv is missing", StatusCode: 401, Code: "error-unauthorized-xpriv-missing"}

// ErrMissingKey is when neither xPriv nor adminXPriv is provided
var ErrMissingKey = models.SPVError{Message: "neither xPriv nor adminXPriv is provided", StatusCode: 404, Code: "error-shared-config-key-missing"}

// ErrMissingAccessKey is when access key is missing
var ErrMissingAccessKey = models.SPVError{Message: "access key is missing", StatusCode: 401, Code: "error-unauthorized-access-key-missing"}

// ErrCouldNotFindDraftTransaction is when draft transaction is not found
var ErrCouldNotFindDraftTransaction = models.SPVError{Message: "could not find draft transaction", StatusCode: 404, Code: "error-draft-transaction-not-found"}

// ErrTotpInvalid is when totp is invalid
var ErrTotpInvalid = models.SPVError{Message: "totp is invalid", StatusCode: 400, Code: "error-totp-invalid"}

// ErrContactPubKeyInvalid is when contact's PubKey is invalid
var ErrContactPubKeyInvalid = models.SPVError{Message: "contact's PubKey is invalid", StatusCode: 400, Code: "error-contact-pubkey-invalid"}

// WrapError wraps an error into SPVError
func WrapError(err error) error {
if err == nil {
return nil
}

return &models.SPVError{
return models.SPVError{
StatusCode: http.StatusInternalServerError,
Message: err.Error(),
Code: models.UnknownErrorCode,
Expand All @@ -34,22 +46,22 @@ func WrapResponseError(res *http.Response) error {
return nil
}

var resError *models.ResponseError
var resError models.ResponseError

err := json.NewDecoder(res.Body).Decode(&resError)
if err != nil {
return WrapError(err)
}

return &models.SPVError{
return models.SPVError{
StatusCode: res.StatusCode,
Code: resError.Code,
Message: resError.Message,
}
}

func CreateErrorResponse(code string, message string) error {
return &models.SPVError{
return models.SPVError{
StatusCode: http.StatusInternalServerError,
Code: code,
Message: message,
Expand Down
1 change: 0 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ In this directory you can find examples of how to use the `spv-wallet-go-client`
In addition to the above, there are additional examples showing how to use the client from a developer perspective:

- `handle_exceptions` - presents how to "catch" exceptions which the client can throw
- `custom_logger` - shows different ways you can configure (or disable) internal logger

## Util examples

Expand Down
9 changes: 6 additions & 3 deletions examples/admin_add_user/admin_add_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ func main() {

metadata := map[string]any{"some_metadata": "example"}

newXPubRes := adminClient.AdminNewXpub(ctx, examples.ExampleXPub, metadata)
fmt.Println("AdminNewXpub response: ", newXPubRes)
err := adminClient.AdminNewXpub(ctx, examples.ExampleXPub, metadata)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
}

createPaymailRes, err := adminClient.AdminCreatePaymail(ctx, examples.ExampleXPub, examples.ExamplePaymail, "Some public name", "")
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("AdminCreatePaymail response: ", createPaymailRes)
Expand Down
3 changes: 1 addition & 2 deletions examples/admin_remove_user/admin_remove_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package main

import (
"context"
"fmt"
"os"

walletclient "github.com/bitcoin-sv/spv-wallet-go-client"
Expand All @@ -24,7 +23,7 @@ func main() {

err := adminClient.AdminDeletePaymail(ctx, examples.ExamplePaymail)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
}
6 changes: 3 additions & 3 deletions examples/create_transaction/create_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ func main() {
client := walletclient.NewWithXPriv(server, examples.ExampleXPriv)
ctx := context.Background()

recipient := walletclient.Recipients{To: "[email protected]", Satoshis: 1}
recipient := walletclient.Recipients{To: "[email protected]", Satoshis: 1}
recipients := []*walletclient.Recipients{&recipient}
metadata := map[string]any{"some_metadata": "example"}

newTransaction, err := client.SendToRecipients(ctx, recipients, metadata)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("SendToRecipients response: ", newTransaction)

tx, err := client.GetTransaction(ctx, newTransaction.ID)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("GetTransaction response: ", tx)
Expand Down
21 changes: 21 additions & 0 deletions examples/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package examples

import (
"errors"
"fmt"

"github.com/bitcoin-sv/spv-wallet/models"
)

// GetFullErrorMessage prints detailed info about the error
func GetFullErrorMessage(err error) {
var errMsg string

var spvError models.SPVError
if errors.As(err, &spvError) {
errMsg = fmt.Sprintf("Error, Message: %s, Code: %s, HTTP status code: %d", spvError.GetMessage(), spvError.GetCode(), spvError.GetStatusCode())
} else {
errMsg = fmt.Sprintf("Error, Message: %s, Code: %s, HTTP status code: %d", err.Error(), models.UnknownErrorCode, 500)
}
fmt.Println(errMsg)
}
2 changes: 1 addition & 1 deletion examples/get_balance/get_balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func main() {

xpubInfo, err := client.GetXPub(ctx)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("Current balance: ", xpubInfo.CurrentBalance)
Expand Down
17 changes: 8 additions & 9 deletions examples/handle_exceptions/handle_exceptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,33 @@ package main

import (
"context"
"errors"
"fmt"
"os"

walletclient "github.com/bitcoin-sv/spv-wallet-go-client"
"github.com/bitcoin-sv/spv-wallet-go-client/examples"
"github.com/bitcoin-sv/spv-wallet/models"
)

func main() {
defer examples.HandlePanic()

fmt.Println("Handle exceptions example")

examples.CheckIfXPubExists()

fmt.Println("XPub exists")

const server = "http://localhost:3003/v1"

client := walletclient.NewWithXPub(server, examples.ExampleXPub)
ctx := context.Background()

fmt.Println("Client created")

status, err := client.AdminGetStatus(ctx)
if err != nil {
var extendedErr models.ExtendedError
if errors.As(err, &extendedErr) {
fmt.Printf("Extended error: [%d] '%s': %s\n", extendedErr.GetStatusCode(), extendedErr.GetCode(), extendedErr.GetMessage())
} else {
fmt.Println("Error: ", err.Error())
}

fmt.Println("Error: ", err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}

Expand Down
7 changes: 4 additions & 3 deletions examples/list_transactions/list_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,18 @@ func main() {

txs, err := client.GetTransactions(ctx, &conditions, metadata, &queryParams)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("GetTransactions response: ", txs)

conditions = filter.TransactionFilter{BlockHeight: func(i uint64) *uint64 { return &i }(839228)}
targetBlockHeight := uint64(839228)
conditions = filter.TransactionFilter{BlockHeight: &targetBlockHeight}
queryParams = filter.QueryParams{PageSize: 100, Page: 1}

txsFiltered, err := client.GetTransactions(ctx, &conditions, metadata, &queryParams)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("Filtered GetTransactions response: ", txsFiltered)
Expand Down
6 changes: 3 additions & 3 deletions examples/send_op_return/send_op_return.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ func main() {

draftTransaction, err := client.DraftTransaction(ctx, &transactionConfig, metadata)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("DraftTransaction response: ", draftTransaction)

finalized, err := client.FinalizeTransaction(draftTransaction)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
transaction, err := client.RecordTransaction(ctx, finalized, draftTransaction.ID, metadata)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("Transaction with OP_RETURN: ", transaction)
Expand Down
3 changes: 2 additions & 1 deletion examples/xpriv_from_mnemonic/xpriv_from_mnemonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package main

import (
"fmt"
"github.com/bitcoin-sv/spv-wallet-go-client/examples"
"os"

"github.com/bitcoin-sv/spv-wallet-go-client/xpriv"
Expand All @@ -16,7 +17,7 @@ func main() {

keys, err := xpriv.FromMnemonic(mnemonicPhrase)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}

Expand Down
3 changes: 2 additions & 1 deletion examples/xpub_from_xpriv/xpub_from_xpriv.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package main

import (
"fmt"
"github.com/bitcoin-sv/spv-wallet-go-client/examples"
"os"

"github.com/bitcoin-sv/spv-wallet-go-client/xpriv"
Expand All @@ -16,7 +17,7 @@ func main() {

keys, err := xpriv.FromString(xPriv)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}

Expand Down
9 changes: 4 additions & 5 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
Expand Down Expand Up @@ -566,7 +565,7 @@ func (wc *WalletClient) authenticateWithXpriv(sign bool, req *http.Request, xPri

func (wc *WalletClient) authenticateWithAccessKey(req *http.Request, rawJSON []byte) error {
if wc.accessKey == nil {
return WrapError(errors.New("access key is missing"))
return ErrMissingAccessKey
}
return SetSignatureFromAccessKey(&req.Header, hex.EncodeToString(wc.accessKey.Serialise()), string(rawJSON))
}
Expand Down Expand Up @@ -597,11 +596,11 @@ func (wc *WalletClient) RejectContact(ctx context.Context, paymail string) error
func (wc *WalletClient) ConfirmContact(ctx context.Context, contact *models.Contact, passcode, requesterPaymail string, period, digits uint) error {
isTotpValid, err := wc.ValidateTotpForContact(contact, passcode, requesterPaymail, period, digits)
if err != nil {
return WrapError(fmt.Errorf("totp validation failed: %w", err))
return WrapError(ErrTotpInvalid)
}

if !isTotpValid {
return WrapError(errors.New("totp is invalid"))
return WrapError(ErrTotpInvalid)
}

if err := wc.doHTTPRequest(
Expand Down Expand Up @@ -666,7 +665,7 @@ func (wc *WalletClient) GetSharedConfig(ctx context.Context) (*models.SharedConf
key = wc.adminXPriv
}
if key == nil {
return nil, WrapError(errors.New("neither xPriv nor adminXPriv is provided"))
return nil, WrapError(ErrMissingKey)
}
if err := wc.doHTTPRequest(
ctx, http.MethodGet, "/shared-config", nil, key, true, &model,
Expand Down
9 changes: 2 additions & 7 deletions totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package walletclient
import (
"encoding/base32"
"encoding/hex"
"errors"
"fmt"
"time"

"github.com/bitcoin-sv/spv-wallet-go-client/utils"
Expand All @@ -16,9 +14,6 @@ import (
"github.com/pquerna/otp/totp"
)

// ErrClientInitNoXpriv error per init client with first xpriv key
var ErrClientInitNoXpriv = errors.New("init client with xPriv first")

const (
// TotpDefaultPeriod - Default number of seconds a TOTP is valid for.
TotpDefaultPeriod uint = 30
Expand Down Expand Up @@ -84,7 +79,7 @@ func getTotpOpts(period, digits uint) *totp.ValidateOpts {

func getSharedSecretFactors(b *WalletClient, c *models.Contact) (*bec.PrivateKey, *bec.PublicKey, error) {
if b.xPriv == nil {
return nil, nil, ErrClientInitNoXpriv
return nil, nil, ErrMissingXpriv
}

xpriv, err := deriveXprivForPki(b.xPriv)
Expand All @@ -99,7 +94,7 @@ func getSharedSecretFactors(b *WalletClient, c *models.Contact) (*bec.PrivateKey

pubKey, err := convertPubKey(c.PubKey)
if err != nil {
return nil, nil, fmt.Errorf("contact's PubKey is invalid: %w", err)
return nil, nil, ErrContactPubKeyInvalid
}

return privKey, pubKey, nil
Expand Down
4 changes: 2 additions & 2 deletions totp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestGenerateTotpForContact(t *testing.T) {
_, err := sut.GenerateTotpForContact(nil, 30, 2)

// then
require.ErrorIs(t, err, ErrClientInitNoXpriv)
require.ErrorIs(t, err, ErrMissingXpriv)
})

t.Run("contact has invalid PubKey - returns error", func(t *testing.T) {
Expand All @@ -50,7 +50,7 @@ func TestGenerateTotpForContact(t *testing.T) {
_, err := sut.GenerateTotpForContact(&contact, 30, 2)

// then
require.ErrorContains(t, err, "contact's PubKey is invalid:")
require.ErrorIs(t, err, ErrContactPubKeyInvalid)

})
}
Expand Down

0 comments on commit c1170bf

Please sign in to comment.