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
This commit is contained in:
Bill Thorp 2020-06-25 17:16:39 -04:00 committed by Michal Niewrzal
parent 4a98c9514c
commit 66661c7486
4 changed files with 89 additions and 22 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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