From 66661c748675e4221eb912b9c927a182f9ec33a0 Mon Sep 17 00:00:00 2001 From: Bill Thorp Date: Thu, 25 Jun 2020 17:16:39 -0400 Subject: [PATCH] satellite/coinpayments: query status of >25 coinpayments Allow more than 25 coin payment statuses to be queries from coinpayments.net Add some slight logging, a coinpayments duration metric, and a disabled test These changes are small changes in support of https://storjlabs.atlassian.net/browse/USR-801 Change-Id: I5b176cdd5e513d8bd88b92f9b22a8bd2456bbdd5 --- .../payments/coinpayments/transactions.go | 45 +++++++++-------- .../coinpayments/transactions_test.go | 48 +++++++++++++++++++ .../payments/stripecoinpayments/service.go | 9 ++-- .../stripecoinpayments/transactions.go | 9 ++++ 4 files changed, 89 insertions(+), 22 deletions(-) create mode 100644 satellite/payments/coinpayments/transactions_test.go diff --git a/satellite/payments/coinpayments/transactions.go b/satellite/payments/coinpayments/transactions.go index 47a4c8fbb..cf83788a2 100644 --- a/satellite/payments/coinpayments/transactions.go +++ b/satellite/payments/coinpayments/transactions.go @@ -285,25 +285,32 @@ func (t Transactions) Info(ctx context.Context, id TransactionID) (*TransactionI return txInfo, nil } -// ListInfos returns up to 25 transaction infos. +// ListInfos returns transaction infos. func (t Transactions) ListInfos(ctx context.Context, ids TransactionIDList) (TransactionInfos, error) { - if len(ids) > 25 { - return nil, Error.New("only up to 25 transactions can be queried") + // The service supports a max batch size of 25 items + const batchSize = 25 + var allErrors error + numIds := len(ids) + txInfos := make(TransactionInfos, numIds) + + for i := 0; i < len(ids); i += batchSize { + j := i + batchSize + if j > numIds { + j = numIds + } + batchInfos := make(TransactionInfos, j-i) + values := make(url.Values, j-i) + values.Set("txid", ids[i:j].Encode()) + res, err := t.client.do(ctx, cmdGetTransactionInfoList, values) + if err != nil { + allErrors = errs.Combine(allErrors, err) + } + if err = json.Unmarshal(res, &batchInfos); err != nil { + allErrors = errs.Combine(allErrors, err) + } + for k, v := range batchInfos { + txInfos[k] = v + } } - - values := make(url.Values) - values.Set("txid", ids.Encode()) - - txInfos := make(TransactionInfos, len(ids)) - - res, err := t.client.do(ctx, cmdGetTransactionInfoList, values) - if err != nil { - return nil, Error.Wrap(err) - } - - if err = json.Unmarshal(res, &txInfos); err != nil { - return nil, Error.Wrap(err) - } - - return txInfos, nil + return txInfos, allErrors } diff --git a/satellite/payments/coinpayments/transactions_test.go b/satellite/payments/coinpayments/transactions_test.go new file mode 100644 index 000000000..15ced557b --- /dev/null +++ b/satellite/payments/coinpayments/transactions_test.go @@ -0,0 +1,48 @@ +// Copyright (C) 2020 Storj Labs, Inc. +// See LICENSE for copying information. + +package coinpayments + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + + "storj.io/common/testcontext" +) + +func TestListInfos(t *testing.T) { + //This test is deliberately skipped as it requires credentials to coinpayments.net + t.SkipNow() + ctx := testcontext.New(t) + defer ctx.Cleanup() + + payments := NewClient(Credentials{ + PublicKey: "ask-littleskunk-on-keybase", + PrivateKey: "ask-littleskunk-on-keybase", + }).Transactions() + + //verify that bad ids fail + infos, err := payments.ListInfos(ctx, TransactionIDList{"an_unlikely_id"}) + assert.Error(t, err) + assert.Len(t, infos, 0) + + //verify that ListInfos can handle more than 25 good ids + ids := TransactionIDList{} + for x := 0; x < 27; x++ { + tx, err := payments.Create(ctx, + &CreateTX{ + Amount: *big.NewFloat(100), + CurrencyIn: CurrencySTORJ, + CurrencyOut: CurrencySTORJ, + BuyerEmail: "test@test.com", + }, + ) + ids = append(ids, tx.ID) + assert.NoError(t, err) + } + infos, err = payments.ListInfos(ctx, ids) + assert.NoError(t, err) + assert.Len(t, infos, 27) +} diff --git a/satellite/payments/stripecoinpayments/service.go b/satellite/payments/stripecoinpayments/service.go index 4fa761f48..065f41c4e 100644 --- a/satellite/payments/stripecoinpayments/service.go +++ b/satellite/payments/stripecoinpayments/service.go @@ -150,7 +150,7 @@ func (service *Service) updateTransactionsLoop(ctx context.Context) (err error) return err } - if err := service.updateTransactions(ctx, txsPage.IDList()); err != nil { + if err := service.updateTransactions(ctx, txsPage.IDList(), txsPage.CreationTimes()); err != nil { return err } @@ -164,7 +164,7 @@ func (service *Service) updateTransactionsLoop(ctx context.Context) (err error) return err } - if err := service.updateTransactions(ctx, txsPage.IDList()); err != nil { + if err := service.updateTransactions(ctx, txsPage.IDList(), txsPage.CreationTimes()); err != nil { return err } } @@ -173,7 +173,7 @@ func (service *Service) updateTransactionsLoop(ctx context.Context) (err error) } // updateTransactions updates statuses and received amount for given transactions. -func (service *Service) updateTransactions(ctx context.Context, ids TransactionAndUserList) (err error) { +func (service *Service) updateTransactions(ctx context.Context, ids TransactionAndUserList, creationTimes map[coinpayments.TransactionID]time.Time) (err error) { defer mon.Task()(&ctx, ids)(&err) if len(ids) == 0 { @@ -190,6 +190,7 @@ func (service *Service) updateTransactions(ctx context.Context, ids TransactionA var applies coinpayments.TransactionIDList for id, info := range infos { + service.log.Debug("Coinpayments results: ", zap.String("status", info.Status.String()), zap.String("id", id.String())) updates = append(updates, TransactionUpdate{ TransactionID: id, @@ -203,6 +204,8 @@ func (service *Service) updateTransactions(ctx context.Context, ids TransactionA // account, so we can apply this amount to customer balance. // Therefore, create intent to update customer balance in the future. if info.Status == coinpayments.StatusCompleted { + //monkit currently does not have a DurationVal + mon.IntVal("coinpayment_duration").Observe(int64(time.Since(creationTimes[id]))) applies = append(applies, id) } diff --git a/satellite/payments/stripecoinpayments/transactions.go b/satellite/payments/stripecoinpayments/transactions.go index e168f2c4d..9c76193eb 100644 --- a/satellite/payments/stripecoinpayments/transactions.go +++ b/satellite/payments/stripecoinpayments/transactions.go @@ -77,6 +77,15 @@ func (page *TransactionsPage) IDList() TransactionAndUserList { return ids } +// CreationTimes returns a map of creation times of page's transactions. +func (page *TransactionsPage) CreationTimes() map[coinpayments.TransactionID]time.Time { + var creationTimes map[coinpayments.TransactionID]time.Time + for _, tx := range page.Transactions { + creationTimes[tx.ID] = tx.CreatedAt + } + return creationTimes +} + // TransactionAndUserList is a composite type for storing userID and txID type TransactionAndUserList map[coinpayments.TransactionID]uuid.UUID