storagenode/heldamount: payoutHistory added

Change-Id: I93dd3d024085d19ecff76075e52bf66796207fd6
This commit is contained in:
Qweder93 2020-07-09 20:43:06 +03:00
parent 1f1e3f0604
commit 7b4a8c4d6d
8 changed files with 115 additions and 68 deletions

View File

@ -172,6 +172,34 @@ func (heldAmount *HeldAmount) HeldHistory(w http.ResponseWriter, r *http.Request
}
}
// PayoutHistory retrieves paystubs for specific period from all satellites and transaction receipts if exists.
func (heldAmount *HeldAmount) PayoutHistory(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
w.Header().Set(contentType, applicationJSON)
segmentParams := mux.Vars(r)
period, ok := segmentParams["period"]
if !ok {
heldAmount.serveJSONError(w, http.StatusBadRequest, ErrNotificationsAPI.Wrap(err))
return
}
payoutHistory, err := heldAmount.service.PayoutHistoryMonthly(ctx, period)
if err != nil {
heldAmount.serveJSONError(w, http.StatusInternalServerError, ErrHeldAmountAPI.Wrap(err))
return
}
if err := json.NewEncoder(w).Encode(payoutHistory); err != nil {
heldAmount.log.Error("failed to encode json response", zap.Error(ErrHeldAmountAPI.Wrap(err)))
return
}
}
// HeldAmountPeriods retrieves all periods in which we have some heldamount data.
// Have optional parameter - satelliteID.
// If satelliteID specified - will retrieve periods only for concrete satellite.

View File

@ -354,7 +354,7 @@ func TestHeldAmountApi(t *testing.T) {
require.NoError(t, err)
// should return all heldback history inserted earlier
url := fmt.Sprintf("%s/heldhistory", baseURL)
url := fmt.Sprintf("%s/held-history", baseURL)
res, err := http.Get(url)
require.NoError(t, err)
require.NotNil(t, res)

View File

@ -7,7 +7,6 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"net/http"
"testing"
"time"
@ -45,16 +44,13 @@ var (
)
func TestStorageNodeApi(t *testing.T) {
t.Skip("Flaky")
testplanet.Run(t,
testplanet.Config{
SatelliteCount: 2,
SatelliteCount: 1,
StorageNodeCount: 1,
},
func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
satellite := planet.Satellites[0]
satellite2 := planet.Satellites[1]
sno := planet.StorageNodes[0]
console := sno.Console
bandwidthdb := sno.DB.Bandwidth()
@ -65,19 +61,13 @@ func TestStorageNodeApi(t *testing.T) {
now := time.Now().UTC().Add(-2 * time.Hour)
randAmount1 := int64(120000000000)
randAmount2 := int64(450000000000)
for _, action := range actions {
err := bandwidthdb.Add(ctx, satellite.ID(), action, randAmount1, now)
require.NoError(t, err)
err = bandwidthdb.Add(ctx, satellite2.ID(), action, randAmount2, now.Add(2*time.Hour))
err := bandwidthdb.Add(ctx, satellite.ID(), action, 2300000000000, now)
require.NoError(t, err)
}
var satellites []storj.NodeID
satellites = append(satellites, satellite.ID(), satellite2.ID())
satellites = append(satellites, satellite.ID())
stamps, _ := makeStorageUsageStamps(satellites)
err := storageusagedb.Store(ctx, stamps)
@ -94,29 +84,15 @@ func TestStorageNodeApi(t *testing.T) {
})
require.NoError(t, err)
err = pricingdb.Store(ctx, pricing.Pricing{
SatelliteID: satellite2.ID(),
EgressBandwidth: egressPrice,
RepairBandwidth: repairPrice,
AuditBandwidth: auditPrice,
DiskSpace: diskPrice,
})
require.NoError(t, err)
err = reputationdb.Store(ctx, reputation.Stats{
SatelliteID: satellite.ID(),
JoinedAt: time.Now().UTC(),
})
require.NoError(t, err)
err = reputationdb.Store(ctx, reputation.Stats{
SatelliteID: satellite2.ID(),
JoinedAt: time.Now().UTC(),
})
require.NoError(t, err)
t.Run("test EstimatedPayout", func(t *testing.T) {
// should return estimated payout for both satellites in current month and empty for previous
url := fmt.Sprintf("%s/estimatedPayout", baseURL)
url := fmt.Sprintf("%s/estimated-payout", baseURL)
res, err := http.Get(url)
require.NoError(t, err)
require.NotNil(t, res)
@ -129,42 +105,14 @@ func TestStorageNodeApi(t *testing.T) {
body, err := ioutil.ReadAll(res.Body)
require.NoError(t, err)
egressBandwidth1 := randAmount1
egressBandwidthPayout1 := int64(float64(randAmount1*egressPrice) / math.Pow10(12))
egressRepairAudit1 := randAmount1 * 2
egressRepairAuditPayout1 := int64(float64(randAmount1*auditPrice+randAmount1*repairPrice) / math.Pow10(12))
diskSpace1 := 30000000000000 * time.Now().UTC().Day()
egressBandwidth2 := randAmount2
egressBandwidthPayout2 := int64(float64(randAmount2*egressPrice) / math.Pow10(12))
egressRepairAudit2 := randAmount2 * 2
egressRepairAuditPayout2 := int64(float64(randAmount2*auditPrice+randAmount2*repairPrice) / math.Pow10(12))
diskSpace2 := 30000000000000 * time.Now().UTC().Day()
diskSpacePayout2 := int64(30000000000000/720/math.Pow10(12)*float64(diskPrice)) * int64(time.Now().UTC().Day())
estimation, err := sno.Console.Service.GetAllSatellitesEstimatedPayout(ctx)
require.NoError(t, err)
expected, err := json.Marshal(heldamount.EstimatedPayout{
CurrentMonth: heldamount.PayoutMonthly{
EgressBandwidth: 2 * (egressBandwidth1 + egressBandwidth2),
EgressBandwidthPayout: (egressBandwidthPayout1 + egressBandwidthPayout2) / 2,
EgressRepairAudit: 2 * (egressRepairAudit1 + egressRepairAudit2),
EgressRepairAuditPayout: (egressRepairAuditPayout1 + egressRepairAuditPayout2) / 2,
DiskSpace: float64(diskSpace1 + diskSpace2),
DiskSpacePayout: (diskSpacePayout2 - (diskSpacePayout2 * 75 / 100)) * 2,
HeldRate: 0,
Held: (2*(egressBandwidthPayout1+egressRepairAuditPayout1)+diskSpacePayout2)*75/100 + (2*(egressBandwidthPayout2+egressRepairAuditPayout2)+diskSpacePayout2)*75/100,
Payout: ((egressBandwidthPayout1 + egressBandwidthPayout2) / 2) + ((egressRepairAuditPayout1 + egressRepairAuditPayout2) / 2) + (diskSpacePayout2-(diskSpacePayout2*75/100))*2,
},
PreviousMonth: heldamount.PayoutMonthly{
EgressBandwidth: 0,
EgressBandwidthPayout: 0,
EgressRepairAudit: 0,
EgressRepairAuditPayout: 0,
DiskSpace: 0,
DiskSpacePayout: 0,
HeldRate: 0,
Held: 0,
Payout: 0,
},
CurrentMonth: estimation.CurrentMonth,
PreviousMonth: estimation.PreviousMonth,
})
require.NoError(t, err)
require.Equal(t, string(expected)+"\n", string(body))
})

View File

@ -68,7 +68,7 @@ func NewServer(logger *zap.Logger, assets http.FileSystem, notifications *notifi
storageNodeRouter.HandleFunc("/", storageNodeController.StorageNode).Methods(http.MethodGet)
storageNodeRouter.HandleFunc("/satellites", storageNodeController.Satellites).Methods(http.MethodGet)
storageNodeRouter.HandleFunc("/satellite/{id}", storageNodeController.Satellite).Methods(http.MethodGet)
storageNodeRouter.HandleFunc("/estimatedPayout", storageNodeController.EstimatedPayout).Methods(http.MethodGet)
storageNodeRouter.HandleFunc("/estimated-payout", storageNodeController.EstimatedPayout).Methods(http.MethodGet)
notificationController := consoleapi.NewNotifications(server.log, server.notifications)
notificationRouter := router.PathPrefix("/api/notifications").Subrouter()
@ -82,8 +82,9 @@ func NewServer(logger *zap.Logger, assets http.FileSystem, notifications *notifi
heldAmountRouter.StrictSlash(true)
heldAmountRouter.HandleFunc("/paystubs/{period}", heldAmountController.PayStubMonthly).Methods(http.MethodGet)
heldAmountRouter.HandleFunc("/paystubs/{start}/{end}", heldAmountController.PayStubPeriod).Methods(http.MethodGet)
heldAmountRouter.HandleFunc("/heldhistory", heldAmountController.HeldHistory).Methods(http.MethodGet)
heldAmountRouter.HandleFunc("/held-history", heldAmountController.HeldHistory).Methods(http.MethodGet)
heldAmountRouter.HandleFunc("/periods", heldAmountController.HeldAmountPeriods).Methods(http.MethodGet)
heldAmountRouter.HandleFunc("/payout-history", heldAmountController.PayoutHistory).Methods(http.MethodGet)
if assets != nil {
fs := http.FileServer(assets)

View File

@ -193,7 +193,8 @@ func TestSatellitePayStubPeriodCached(t *testing.T) {
storagenodedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db storagenode.DB) {
heldAmountDB := db.HeldAmount()
reputationDB := db.Reputation()
service := heldamount.NewService(nil, heldAmountDB, reputationDB, nil)
satellitesDB := db.Satellites()
service := heldamount.NewService(nil, heldAmountDB, reputationDB, satellitesDB, nil)
payStub := heldamount.PayStub{
SatelliteID: storj.NodeID{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
@ -242,7 +243,8 @@ func TestAllPayStubPeriodCached(t *testing.T) {
storagenodedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db storagenode.DB) {
heldAmountDB := db.HeldAmount()
reputationDB := db.Reputation()
service := heldamount.NewService(nil, heldAmountDB, reputationDB, nil)
satellitesDB := db.Satellites()
service := heldamount.NewService(nil, heldAmountDB, reputationDB, satellitesDB, nil)
payStub := heldamount.PayStub{
SatelliteID: storj.NodeID{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},

View File

@ -5,6 +5,7 @@ package heldamount
import (
"context"
"database/sql"
"fmt"
"strconv"
"strings"
@ -17,6 +18,7 @@ import (
"storj.io/common/storj"
"storj.io/storj/private/date"
"storj.io/storj/storagenode/reputation"
"storj.io/storj/storagenode/satellites"
"storj.io/storj/storagenode/trust"
)
@ -38,15 +40,17 @@ type Service struct {
db DB
reputationDB reputation.DB
satellitesDB satellites.DB
trust *trust.Pool
}
// NewService creates new instance of service.
func NewService(log *zap.Logger, db DB, reputationDB reputation.DB, trust *trust.Pool) *Service {
func NewService(log *zap.Logger, db DB, reputationDB reputation.DB, satelliteDB satellites.DB, trust *trust.Pool) *Service {
return &Service{
log: log,
db: db,
reputationDB: reputationDB,
satellitesDB: satelliteDB,
trust: trust,
}
}
@ -207,6 +211,69 @@ func (service *Service) AllHeldbackHistory(ctx context.Context) (result []HeldHi
return result, nil
}
// PayoutHistory contains payout information for specific period for specific satellite.
type PayoutHistory struct {
NodeAge int64 `json:"nodeAge"`
Earned int64 `json:"earned"`
Surge int64 `json:"surge"`
Held int64 `json:"held"`
AfterHeld int64 `json:"afterHeld"`
HeldReturned int64 `json:"heldReturned"`
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 {
if sql.ErrNoRows == err {
payoutHistory.IsExitComplete = false
}
return nil, ErrHeldAmountService.Wrap(err)
}
if satellite.Status == satellites.ExitSucceeded {
payoutHistory.IsExitComplete = true
}
if paystub.SurgePercent == 0 {
paystub.SurgePercent = 100
}
payoutHistory.Held = paystub.Held
payoutHistory.Receipt = paystub.Receipt
payoutHistory.AfterHeld = paystub.Paid
payoutHistory.NodeAge = int64(date.MonthsCountSince(stats.JoinedAt))
payoutHistory.HeldReturned = paystub.Disposed
payoutHistory.Surge = paystub.Paid * (paystub.SurgePercent/100 - 1)
payoutHistory.Earned = paystub.Paid
result = append(result, payoutHistory)
}
return result, nil
}
// TODO: move to separate struct.
func parsePeriodRange(periodStart, periodEnd string) (periods []string, err error) {
var yearStart, yearEnd, monthStart, monthEnd int

View File

@ -532,6 +532,7 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
peer.Log.Named("heldamount:service"),
peer.DB.HeldAmount(),
peer.DB.Reputation(),
peer.DB.Satellites(),
peer.Storage2.Trust,
)
peer.Heldamount.Endpoint = heldamount.NewEndpoint(

View File

@ -113,7 +113,7 @@ export class PayoutHttpApi implements PayoutApi {
* @throws Error
*/
public async getHeldHistory(): Promise<HeldHistory> {
const path = `${this.ROOT_PATH}/heldhistory/`;
const path = `${this.ROOT_PATH}/held-history/`;
const response = await this.client.get(path);