2020-03-13 14:01:12 +00:00
|
|
|
// Copyright (C) 2020 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package heldamount
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-07-09 18:43:06 +01:00
|
|
|
"database/sql"
|
2020-07-16 16:50:15 +01:00
|
|
|
"errors"
|
2020-03-16 04:28:03 +00:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2020-06-24 16:57:22 +01:00
|
|
|
"time"
|
2020-03-13 14:01:12 +00:00
|
|
|
|
|
|
|
"github.com/spacemonkeygo/monkit/v3"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2020-05-29 09:52:10 +01:00
|
|
|
"storj.io/common/storj"
|
2020-03-18 11:27:42 +00:00
|
|
|
"storj.io/storj/private/date"
|
2020-05-27 17:27:28 +01:00
|
|
|
"storj.io/storj/storagenode/reputation"
|
2020-07-09 18:43:06 +01:00
|
|
|
"storj.io/storj/storagenode/satellites"
|
2020-03-13 14:01:12 +00:00
|
|
|
"storj.io/storj/storagenode/trust"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-03-26 14:53:15 +00:00
|
|
|
// ErrHeldAmountService defines held amount service error.
|
2020-03-15 23:24:04 +00:00
|
|
|
ErrHeldAmountService = errs.Class("heldamount service error")
|
2020-03-13 14:01:12 +00:00
|
|
|
|
2020-03-26 14:53:15 +00:00
|
|
|
// ErrBadPeriod defines that period has wrong format.
|
|
|
|
ErrBadPeriod = errs.Class("wrong period format")
|
|
|
|
|
2020-03-13 14:01:12 +00:00
|
|
|
mon = monkit.Package()
|
|
|
|
)
|
|
|
|
|
|
|
|
// Service retrieves info from satellites using an rpc client
|
|
|
|
//
|
|
|
|
// architecture: Service
|
|
|
|
type Service struct {
|
|
|
|
log *zap.Logger
|
|
|
|
|
2020-05-27 17:27:28 +01:00
|
|
|
db DB
|
|
|
|
reputationDB reputation.DB
|
2020-07-09 18:43:06 +01:00
|
|
|
satellitesDB satellites.DB
|
2020-07-02 14:54:32 +01:00
|
|
|
trust *trust.Pool
|
2020-03-13 14:01:12 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 11:57:02 +01:00
|
|
|
// NewService creates new instance of service.
|
2020-07-09 18:43:06 +01:00
|
|
|
func NewService(log *zap.Logger, db DB, reputationDB reputation.DB, satelliteDB satellites.DB, trust *trust.Pool) *Service {
|
2020-03-13 14:01:12 +00:00
|
|
|
return &Service{
|
2020-05-27 17:27:28 +01:00
|
|
|
log: log,
|
|
|
|
db: db,
|
|
|
|
reputationDB: reputationDB,
|
2020-07-09 18:43:06 +01:00
|
|
|
satellitesDB: satelliteDB,
|
2020-05-27 17:27:28 +01:00
|
|
|
trust: trust,
|
2020-03-13 14:01:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-02 14:54:32 +01:00
|
|
|
// SatellitePayStubMonthly retrieves held amount for particular satellite for selected month from storagenode database.
|
|
|
|
func (service *Service) SatellitePayStubMonthly(ctx context.Context, satelliteID storj.NodeID, period string) (payStub *PayStub, err error) {
|
2020-03-15 23:24:04 +00:00
|
|
|
defer mon.Task()(&ctx, &satelliteID, &period)(&err)
|
|
|
|
|
2020-03-16 01:32:52 +00:00
|
|
|
payStub, err = service.db.GetPayStub(ctx, satelliteID, period)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-07-11 20:25:05 +01:00
|
|
|
payStub.UsageAtRestTbM()
|
|
|
|
|
2020-03-16 01:32:52 +00:00
|
|
|
return payStub, nil
|
|
|
|
}
|
|
|
|
|
2020-07-02 14:54:32 +01:00
|
|
|
// AllPayStubsMonthly retrieves held amount for all satellites per selected period from storagenode database.
|
|
|
|
func (service *Service) AllPayStubsMonthly(ctx context.Context, period string) (payStubs []PayStub, err error) {
|
2020-03-16 01:32:52 +00:00
|
|
|
defer mon.Task()(&ctx, &period)(&err)
|
|
|
|
|
|
|
|
payStubs, err = service.db.AllPayStubs(ctx, period)
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return payStubs, ErrHeldAmountService.Wrap(err)
|
2020-03-16 01:32:52 +00:00
|
|
|
}
|
|
|
|
|
2020-07-11 20:25:05 +01:00
|
|
|
for i := 0; i < len(payStubs); i++ {
|
|
|
|
payStubs[i].UsageAtRestTbM()
|
|
|
|
}
|
|
|
|
|
2020-03-16 01:32:52 +00:00
|
|
|
return payStubs, nil
|
2020-03-15 23:24:04 +00:00
|
|
|
}
|
|
|
|
|
2020-07-02 14:54:32 +01:00
|
|
|
// SatellitePayStubPeriod retrieves held amount for all satellites for selected months from storagenode database.
|
|
|
|
func (service *Service) SatellitePayStubPeriod(ctx context.Context, satelliteID storj.NodeID, periodStart, periodEnd string) (payStubs []PayStub, err error) {
|
2020-03-16 04:28:03 +00:00
|
|
|
defer mon.Task()(&ctx, &satelliteID, &periodStart, &periodEnd)(&err)
|
|
|
|
|
|
|
|
periods, err := parsePeriodRange(periodStart, periodEnd)
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return []PayStub{}, err
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, period := range periods {
|
|
|
|
payStub, err := service.db.GetPayStub(ctx, satelliteID, period)
|
|
|
|
if err != nil {
|
|
|
|
if ErrNoPayStubForPeriod.Has(err) {
|
|
|
|
continue
|
|
|
|
}
|
2020-03-26 14:53:15 +00:00
|
|
|
|
|
|
|
return []PayStub{}, ErrHeldAmountService.Wrap(err)
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 14:53:15 +00:00
|
|
|
payStubs = append(payStubs, *payStub)
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
2020-07-11 20:25:05 +01:00
|
|
|
for i := 0; i < len(payStubs); i++ {
|
|
|
|
payStubs[i].UsageAtRestTbM()
|
|
|
|
}
|
|
|
|
|
2020-03-16 04:28:03 +00:00
|
|
|
return payStubs, nil
|
|
|
|
}
|
|
|
|
|
2020-07-02 14:54:32 +01:00
|
|
|
// AllPayStubsPeriod retrieves held amount for all satellites for selected range of months from storagenode database.
|
|
|
|
func (service *Service) AllPayStubsPeriod(ctx context.Context, periodStart, periodEnd string) (payStubs []PayStub, err error) {
|
2020-03-16 04:28:03 +00:00
|
|
|
defer mon.Task()(&ctx, &periodStart, &periodEnd)(&err)
|
|
|
|
|
|
|
|
periods, err := parsePeriodRange(periodStart, periodEnd)
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return []PayStub{}, err
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, period := range periods {
|
|
|
|
payStub, err := service.db.AllPayStubs(ctx, period)
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
if ErrNoPayStubForPeriod.Has(err) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return []PayStub{}, ErrHeldAmountService.Wrap(err)
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
payStubs = append(payStubs, payStub...)
|
|
|
|
}
|
|
|
|
|
2020-07-11 20:25:05 +01:00
|
|
|
for i := 0; i < len(payStubs); i++ {
|
|
|
|
payStubs[i].UsageAtRestTbM()
|
|
|
|
}
|
|
|
|
|
2020-03-16 04:28:03 +00:00
|
|
|
return payStubs, nil
|
|
|
|
}
|
|
|
|
|
2020-05-18 19:37:16 +01:00
|
|
|
// SatellitePeriods retrieves all periods for concrete satellite in which we have some heldamount data.
|
|
|
|
func (service *Service) SatellitePeriods(ctx context.Context, satelliteID storj.NodeID) (_ []string, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
return service.db.SatellitePeriods(ctx, satelliteID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllPeriods retrieves all periods in which we have some heldamount data.
|
|
|
|
func (service *Service) AllPeriods(ctx context.Context) (_ []string, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
return service.db.AllPeriods(ctx)
|
|
|
|
}
|
|
|
|
|
2020-05-27 17:27:28 +01:00
|
|
|
// HeldHistory amount of held for specific percent rate period.
|
|
|
|
type HeldHistory struct {
|
|
|
|
SatelliteID storj.NodeID `json:"satelliteID"`
|
|
|
|
SatelliteName string `json:"satelliteName"`
|
|
|
|
Age int64 `json:"age"`
|
|
|
|
FirstPeriod int64 `json:"firstPeriod"`
|
|
|
|
SecondPeriod int64 `json:"secondPeriod"`
|
|
|
|
ThirdPeriod int64 `json:"thirdPeriod"`
|
2020-06-24 16:57:22 +01:00
|
|
|
TotalHeld int64 `json:"totalHeld"`
|
|
|
|
TotalDisposed int64 `json:"totalDisposed"`
|
|
|
|
JoinedAt time.Time `json:"joinedAt"`
|
2020-04-28 14:07:50 +01:00
|
|
|
}
|
|
|
|
|
2020-05-27 17:27:28 +01:00
|
|
|
// AllHeldbackHistory retrieves heldback history for all satellites from storagenode database.
|
|
|
|
func (service *Service) AllHeldbackHistory(ctx context.Context) (result []HeldHistory, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2020-04-28 14:07:50 +01:00
|
|
|
|
2020-05-27 17:27:28 +01:00
|
|
|
satellites := service.trust.GetSatellites(ctx)
|
|
|
|
for i := 0; i < len(satellites); i++ {
|
|
|
|
var history HeldHistory
|
2020-04-28 14:07:50 +01:00
|
|
|
|
2020-05-27 17:27:28 +01:00
|
|
|
heldback, err := service.db.SatellitesHeldbackHistory(ctx, satellites[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
2020-04-28 14:07:50 +01:00
|
|
|
}
|
|
|
|
|
2020-06-24 16:57:22 +01:00
|
|
|
disposed, err := service.db.SatellitesDisposedHistory(ctx, satellites[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-05-27 17:27:28 +01:00
|
|
|
for i, t := range heldback {
|
|
|
|
switch i {
|
|
|
|
case 0, 1, 2:
|
|
|
|
history.FirstPeriod += t.Held
|
2020-06-24 16:57:22 +01:00
|
|
|
history.TotalHeld += t.Held
|
2020-05-27 17:27:28 +01:00
|
|
|
case 3, 4, 5:
|
|
|
|
history.SecondPeriod += t.Held
|
2020-06-24 16:57:22 +01:00
|
|
|
history.TotalHeld += t.Held
|
2020-05-27 17:27:28 +01:00
|
|
|
case 6, 7, 8:
|
|
|
|
history.ThirdPeriod += t.Held
|
2020-06-24 16:57:22 +01:00
|
|
|
history.TotalHeld += t.Held
|
2020-05-27 17:27:28 +01:00
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-24 16:57:22 +01:00
|
|
|
history.TotalDisposed = disposed
|
2020-05-27 17:27:28 +01:00
|
|
|
history.SatelliteID = satellites[i]
|
|
|
|
url, err := service.trust.GetNodeURL(ctx, satellites[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stats, err := service.reputationDB.Get(ctx, satellites[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
2020-04-28 14:07:50 +01:00
|
|
|
|
2020-05-27 17:27:28 +01:00
|
|
|
history.Age = int64(date.MonthsCountSince(stats.JoinedAt))
|
2020-06-30 16:44:52 +01:00
|
|
|
history.SatelliteName = url.Address
|
2020-06-24 16:57:22 +01:00
|
|
|
history.JoinedAt = stats.JoinedAt
|
2020-04-28 14:07:50 +01:00
|
|
|
|
2020-05-27 17:27:28 +01:00
|
|
|
result = append(result, history)
|
2020-04-28 14:07:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2020-07-09 18:43:06 +01:00
|
|
|
// PayoutHistory contains payout information for specific period for specific satellite.
|
|
|
|
type PayoutHistory struct {
|
2020-07-24 16:50:35 +01:00
|
|
|
SatelliteID string `json:"satelliteID"`
|
|
|
|
SatelliteURL string `json:"satelliteURL"`
|
|
|
|
Age int64 `json:"age"`
|
2020-07-09 18:43:06 +01:00
|
|
|
Earned int64 `json:"earned"`
|
|
|
|
Surge int64 `json:"surge"`
|
2020-07-24 16:50:35 +01:00
|
|
|
SurgePercent int64 `json:"surgePercent"`
|
2020-07-09 18:43:06 +01:00
|
|
|
Held int64 `json:"held"`
|
|
|
|
AfterHeld int64 `json:"afterHeld"`
|
2020-07-24 16:50:35 +01:00
|
|
|
Disposed int64 `json:"disposed"`
|
2020-08-04 13:32:47 +01:00
|
|
|
Paid int64 `json:"paid"`
|
2020-07-09 18:43:06 +01:00
|
|
|
Receipt string `json:"receipt"`
|
|
|
|
IsExitComplete bool `json:"isExitComplete"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// PayoutHistoryMonthly retrieves paystub and payment receipt for specific month from all satellites.
|
|
|
|
func (service *Service) PayoutHistoryMonthly(ctx context.Context, period string) (result []PayoutHistory, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
satelliteIDs := service.trust.GetSatellites(ctx)
|
|
|
|
for i := 0; i < len(satelliteIDs); i++ {
|
|
|
|
var payoutHistory PayoutHistory
|
|
|
|
paystub, err := service.db.GetPayStub(ctx, satelliteIDs[i], period)
|
|
|
|
if err != nil {
|
|
|
|
if ErrNoPayStubForPeriod.Has(err) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stats, err := service.reputationDB.Get(ctx, satelliteIDs[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
satellite, err := service.satellitesDB.GetSatellite(ctx, satelliteIDs[i])
|
|
|
|
if err != nil {
|
2020-07-16 16:50:15 +01:00
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
2020-07-09 18:43:06 +01:00
|
|
|
payoutHistory.IsExitComplete = false
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-07-24 16:50:35 +01:00
|
|
|
url, err := service.trust.GetNodeURL(ctx, satelliteIDs[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-07-09 18:43:06 +01:00
|
|
|
if satellite.Status == satellites.ExitSucceeded {
|
|
|
|
payoutHistory.IsExitComplete = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if paystub.SurgePercent == 0 {
|
|
|
|
paystub.SurgePercent = 100
|
|
|
|
}
|
|
|
|
|
2020-08-04 13:32:47 +01:00
|
|
|
earned := paystub.CompGetAudit + paystub.CompGet + paystub.CompGetRepair + paystub.CompAtRest
|
|
|
|
surge := earned * paystub.SurgePercent / 100
|
|
|
|
|
2020-07-09 18:43:06 +01:00
|
|
|
payoutHistory.Held = paystub.Held
|
|
|
|
payoutHistory.Receipt = paystub.Receipt
|
2020-08-04 13:32:47 +01:00
|
|
|
payoutHistory.Surge = surge
|
|
|
|
payoutHistory.AfterHeld = surge - paystub.Held
|
2020-07-24 16:50:35 +01:00
|
|
|
payoutHistory.Age = int64(date.MonthsCountSince(stats.JoinedAt))
|
|
|
|
payoutHistory.Disposed = paystub.Disposed
|
2020-08-04 13:32:47 +01:00
|
|
|
payoutHistory.Earned = earned
|
2020-07-24 16:50:35 +01:00
|
|
|
payoutHistory.SatelliteID = satelliteIDs[i].String()
|
|
|
|
payoutHistory.SurgePercent = paystub.SurgePercent
|
|
|
|
payoutHistory.SatelliteURL = url.Address
|
2020-08-04 13:32:47 +01:00
|
|
|
payoutHistory.Paid = paystub.Paid
|
2020-07-09 18:43:06 +01:00
|
|
|
|
|
|
|
result = append(result, payoutHistory)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2020-03-26 14:53:15 +00:00
|
|
|
// TODO: move to separate struct.
|
2020-03-16 04:28:03 +00:00
|
|
|
func parsePeriodRange(periodStart, periodEnd string) (periods []string, err error) {
|
|
|
|
var yearStart, yearEnd, monthStart, monthEnd int
|
|
|
|
|
|
|
|
start := strings.Split(periodStart, "-")
|
|
|
|
if len(start) != 2 {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period start has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
end := strings.Split(periodEnd, "-")
|
|
|
|
if len(start) != 2 {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period end has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
yearStart, err = strconv.Atoi(start[0])
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period start has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
monthStart, err = strconv.Atoi(start[1])
|
|
|
|
if err != nil || monthStart > 12 || monthStart < 1 {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period start has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
yearEnd, err = strconv.Atoi(end[0])
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period end has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
monthEnd, err = strconv.Atoi(end[1])
|
|
|
|
if err != nil || monthEnd > 12 || monthEnd < 1 {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period end has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
if yearEnd < yearStart {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
if yearEnd == yearStart && monthEnd < monthStart {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for ; yearStart <= yearEnd; yearStart++ {
|
|
|
|
lastMonth := 12
|
|
|
|
if yearStart == yearEnd {
|
|
|
|
lastMonth = monthEnd
|
|
|
|
}
|
|
|
|
for ; monthStart <= lastMonth; monthStart++ {
|
|
|
|
format := "%d-%d"
|
|
|
|
if monthStart < 10 {
|
|
|
|
format = "%d-0%d"
|
|
|
|
}
|
|
|
|
periods = append(periods, fmt.Sprintf(format, yearStart, monthStart))
|
|
|
|
}
|
|
|
|
|
|
|
|
monthStart = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return periods, nil
|
|
|
|
}
|
2020-07-11 20:25:05 +01:00
|
|
|
|
|
|
|
// UsageAtRestTbM converts paystub's usage_at_rest from tbh to tbm.
|
|
|
|
func (paystub *PayStub) UsageAtRestTbM() {
|
|
|
|
paystub.UsageAtRest /= 720
|
|
|
|
}
|