satellitedb: add token balance to API endpoint
Add the users current wallet balance to the endpoints for claiming and listing storjscan wallets. Also prevent a user with a claimed wallet address from claiming a new wallet. Change-Id: I0dbf1303699f924d05c8c52359038dc5ef6c42a1
This commit is contained in:
parent
119e61fcb0
commit
335e11dacd
@ -579,6 +579,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
peer.Marketing.PartnersService,
|
||||
peer.Payments.Accounts,
|
||||
peer.Payments.DepositWallets,
|
||||
peer.DB.Billing(),
|
||||
peer.Analytics.Service,
|
||||
peer.Console.AuthTokens,
|
||||
peer.Mail.Service,
|
||||
|
@ -108,6 +108,7 @@ func TestGraphqlMutation(t *testing.T) {
|
||||
paymentsService.Accounts(),
|
||||
// TODO: do we need a payment deposit wallet here?
|
||||
nil,
|
||||
db.Billing(),
|
||||
analyticsService,
|
||||
consoleauth.NewService(consoleauth.Config{
|
||||
TokenExpirationTime: 24 * time.Hour,
|
||||
|
@ -92,6 +92,7 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
paymentsService.Accounts(),
|
||||
// TODO: do we need a payment deposit wallet here?
|
||||
nil,
|
||||
db.Billing(),
|
||||
analyticsService,
|
||||
consoleauth.NewService(consoleauth.Config{
|
||||
TokenExpirationTime: 24 * time.Hour,
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"sort"
|
||||
@ -34,6 +33,7 @@ import (
|
||||
"storj.io/storj/satellite/console/consoleauth"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/payments"
|
||||
"storj.io/storj/satellite/payments/billing"
|
||||
"storj.io/storj/satellite/payments/monetary"
|
||||
"storj.io/storj/satellite/rewards"
|
||||
)
|
||||
@ -126,6 +126,7 @@ type Service struct {
|
||||
partners *rewards.PartnersService
|
||||
accounts payments.Accounts
|
||||
depositWallets payments.DepositWallets
|
||||
billing billing.TransactionsDB
|
||||
registrationCaptchaHandler CaptchaHandler
|
||||
loginCaptchaHandler CaptchaHandler
|
||||
analytics *analytics.Service
|
||||
@ -195,7 +196,7 @@ type Payments struct {
|
||||
}
|
||||
|
||||
// NewService returns new instance of Service.
|
||||
func NewService(log *zap.Logger, store DB, restKeys RESTKeys, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, buckets Buckets, partners *rewards.PartnersService, accounts payments.Accounts, depositWallets payments.DepositWallets, analytics *analytics.Service, tokens *consoleauth.Service, mailService *mailservice.Service, satelliteAddress string, config Config) (*Service, error) {
|
||||
func NewService(log *zap.Logger, store DB, restKeys RESTKeys, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, buckets Buckets, partners *rewards.PartnersService, accounts payments.Accounts, depositWallets payments.DepositWallets, billing billing.TransactionsDB, analytics *analytics.Service, tokens *consoleauth.Service, mailService *mailservice.Service, satelliteAddress string, config Config) (*Service, error) {
|
||||
if store == nil {
|
||||
return nil, errs.New("store can't be nil")
|
||||
}
|
||||
@ -234,6 +235,7 @@ func NewService(log *zap.Logger, store DB, restKeys RESTKeys, projectAccounting
|
||||
partners: partners,
|
||||
accounts: accounts,
|
||||
depositWallets: depositWallets,
|
||||
billing: billing,
|
||||
registrationCaptchaHandler: registrationCaptchaHandler,
|
||||
loginCaptchaHandler: loginCaptchaHandler,
|
||||
analytics: analytics,
|
||||
@ -2744,7 +2746,7 @@ func (s *Service) isProjectMember(ctx context.Context, userID uuid.UUID, project
|
||||
// WalletInfo contains all the information about a destination wallet assigned to a user.
|
||||
type WalletInfo struct {
|
||||
Address blockchain.Address `json:"address"`
|
||||
Balance *big.Int `json:"balance"`
|
||||
Balance string `json:"balance"`
|
||||
}
|
||||
|
||||
// PaymentInfo includes token payment information required by GUI.
|
||||
@ -2785,9 +2787,13 @@ func (payment Payments) ClaimWallet(ctx context.Context) (_ WalletInfo, err erro
|
||||
if err != nil {
|
||||
return WalletInfo{}, Error.Wrap(err)
|
||||
}
|
||||
balance, err := payment.service.billing.GetBalance(ctx, user.ID)
|
||||
if err != nil {
|
||||
return WalletInfo{}, Error.Wrap(err)
|
||||
}
|
||||
return WalletInfo{
|
||||
Address: address,
|
||||
Balance: nil, // TODO: populate with call to billing table
|
||||
Balance: balance.AsDecimal().String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -2803,9 +2809,13 @@ func (payment Payments) GetWallet(ctx context.Context) (_ WalletInfo, err error)
|
||||
if err != nil {
|
||||
return WalletInfo{}, Error.Wrap(err)
|
||||
}
|
||||
balance, err := payment.service.billing.GetBalance(ctx, user.ID)
|
||||
if err != nil {
|
||||
return WalletInfo{}, Error.Wrap(err)
|
||||
}
|
||||
return WalletInfo{
|
||||
Address: address,
|
||||
Balance: nil, // TODO: populate with call to billing table
|
||||
Balance: balance.AsDecimal().String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
@ -911,13 +910,13 @@ func TestLockAccount(t *testing.T) {
|
||||
func TestWalletJsonMarshall(t *testing.T) {
|
||||
wi := console.WalletInfo{
|
||||
Address: blockchain.Address{1, 2, 3},
|
||||
Balance: big.NewInt(100),
|
||||
Balance: monetary.AmountFromBaseUnits(10000, monetary.USDollars).AsDecimal().String(),
|
||||
}
|
||||
|
||||
out, err := json.Marshal(wi)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(out), "\"address\":\"0102030000000000000000000000000000000000\"")
|
||||
require.Contains(t, string(out), "\"balance\":100")
|
||||
require.Contains(t, string(out), "\"balance\":\"100\"")
|
||||
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ func (chore *Chore) Run(ctx context.Context) (err error) {
|
||||
|
||||
for _, paymentType := range chore.paymentTypes {
|
||||
lastTransactionTime, lastTransactionMetadata, err := chore.transactionsDB.LastTransaction(ctx, paymentType.Source(), paymentType.Type())
|
||||
if err != nil {
|
||||
if err != nil && !errs.Is(err, ErrNoTransactions) {
|
||||
chore.log.Error("unable to determine timestamp of last transaction", zap.Error(ChoreErr.Wrap(err)))
|
||||
continue
|
||||
}
|
||||
|
@ -19,6 +19,12 @@ type TransactionStatus string
|
||||
// ErrInsufficientFunds represents err when a user balance is too low for some transaction.
|
||||
var ErrInsufficientFunds = errs.New("Insufficient funds for this transaction")
|
||||
|
||||
// ErrNoWallet represents err when there is no wallet in the DB.
|
||||
var ErrNoWallet = errs.New("no wallet in the database")
|
||||
|
||||
// ErrNoTransactions represents err when there is no billing transactions in the DB.
|
||||
var ErrNoTransactions = errs.New("no transactions in the database")
|
||||
|
||||
const (
|
||||
// TransactionStatusPending indicates that status of this transaction is pending.
|
||||
TransactionStatusPending = "pending"
|
||||
|
@ -55,6 +55,16 @@ func NewService(log *zap.Logger, walletsDB WalletsDB, paymentsDB PaymentsDB, cli
|
||||
func (service *Service) Claim(ctx context.Context, userID uuid.UUID) (_ blockchain.Address, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
wallet, err := service.walletsDB.GetWallet(ctx, userID)
|
||||
switch {
|
||||
case err == nil:
|
||||
return wallet, nil
|
||||
case errs.Is(err, billing.ErrNoWallet):
|
||||
// do nothing and continue
|
||||
default:
|
||||
return blockchain.Address{}, err
|
||||
}
|
||||
|
||||
address, err := service.client.ClaimNewEthAddress(ctx)
|
||||
if err != nil {
|
||||
return blockchain.Address{}, ErrService.Wrap(err)
|
||||
@ -113,11 +123,13 @@ func (service *Service) Type() billing.TransactionType {
|
||||
return billing.TransactionTypeCredit
|
||||
}
|
||||
|
||||
// GetNewTransactions returns the storjscan payments since the given timestamp as billing transactions type.
|
||||
// GetNewTransactions returns the storjscan payments since the given block number and index as billing transactions type.
|
||||
func (service *Service) GetNewTransactions(ctx context.Context, _ time.Time, lastPaymentMetadata []byte) ([]billing.Transaction, error) {
|
||||
|
||||
var latestMetadata storjscanMetadata
|
||||
if err := json.Unmarshal(lastPaymentMetadata, &latestMetadata); err != nil {
|
||||
if lastPaymentMetadata == nil {
|
||||
latestMetadata = storjscanMetadata{}
|
||||
} else if err := json.Unmarshal(lastPaymentMetadata, &latestMetadata); err != nil {
|
||||
service.log.Error("error retrieving metadata from latest recorded billing payment")
|
||||
return nil, err
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/common/testrand"
|
||||
"storj.io/storj/private/blockchain"
|
||||
"storj.io/storj/satellite"
|
||||
"storj.io/storj/satellite/payments"
|
||||
"storj.io/storj/satellite/payments/monetary"
|
||||
@ -130,6 +132,53 @@ func TestServicePayments(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestServiceWallets(t *testing.T) {
|
||||
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
||||
userID1 := testrand.UUID()
|
||||
userID2 := testrand.UUID()
|
||||
userID3 := testrand.UUID()
|
||||
walletAddress1, err := blockchain.BytesToAddress(testrand.Bytes(20))
|
||||
require.NoError(t, err)
|
||||
walletAddress2, err := blockchain.BytesToAddress(testrand.Bytes(20))
|
||||
require.NoError(t, err)
|
||||
walletAddress3, err := blockchain.BytesToAddress(testrand.Bytes(20))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Wallets().Add(ctx, userID1, walletAddress1)
|
||||
require.NoError(t, err)
|
||||
err = db.Wallets().Add(ctx, userID2, walletAddress2)
|
||||
require.NoError(t, err)
|
||||
err = db.Wallets().Add(ctx, userID3, walletAddress3)
|
||||
require.NoError(t, err)
|
||||
|
||||
service := storjscan.NewService(zaptest.NewLogger(t), db.Wallets(), db.StorjscanPayments(), nil)
|
||||
|
||||
t.Run("get Wallet", func(t *testing.T) {
|
||||
actual, err := service.Get(ctx, userID1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, walletAddress1, actual)
|
||||
|
||||
actual, err = service.Get(ctx, userID2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, walletAddress2, actual)
|
||||
|
||||
actual, err = service.Get(ctx, userID3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, walletAddress3, actual)
|
||||
})
|
||||
t.Run("claim Wallet already assigned", func(t *testing.T) {
|
||||
actual, err := service.Get(ctx, userID1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, walletAddress1, actual)
|
||||
|
||||
actual, err = service.Claim(ctx, userID1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, walletAddress1, actual)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestListPayments(t *testing.T) {
|
||||
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
||||
paymentsDB := db.StorjscanPayments()
|
||||
|
@ -86,6 +86,7 @@ func TestSignupCouponCodes(t *testing.T) {
|
||||
paymentsService.Accounts(),
|
||||
// TODO: do we need a payment deposit wallet here?
|
||||
nil,
|
||||
db.Billing(),
|
||||
analyticsService,
|
||||
consoleauth.NewService(consoleauth.Config{
|
||||
TokenExpirationTime: 24 * time.Hour,
|
||||
|
@ -135,7 +135,7 @@ func (db billingDB) LastTransaction(ctx context.Context, txSource string, txType
|
||||
}
|
||||
|
||||
if lastTransaction == nil {
|
||||
return time.Time{}, []byte{}, nil
|
||||
return time.Time{}, nil, billing.ErrNoTransactions
|
||||
}
|
||||
|
||||
return lastTransaction.Timestamp, lastTransaction.Metadata, nil
|
||||
|
@ -5,9 +5,12 @@ package satellitedb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/private/blockchain"
|
||||
"storj.io/storj/satellite/payments/billing"
|
||||
"storj.io/storj/satellite/payments/storjscan"
|
||||
"storj.io/storj/satellite/satellitedb/dbx"
|
||||
)
|
||||
@ -35,6 +38,9 @@ func (walletsDB storjscanWalletsDB) GetWallet(ctx context.Context, userID uuid.U
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
wallet, err := walletsDB.db.Get_StorjscanWallet_WalletAddress_By_UserId(ctx, dbx.StorjscanWallet_UserId(userID[:]))
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return blockchain.Address{}, billing.ErrNoWallet
|
||||
}
|
||||
return blockchain.Address{}, Error.Wrap(err)
|
||||
}
|
||||
address, err := blockchain.BytesToAddress(wallet.WalletAddress)
|
||||
|
Loading…
Reference in New Issue
Block a user