2019-01-17 18:34:13 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package payments
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/csv"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
|
|
|
|
|
|
|
"storj.io/storj/pkg/accounting"
|
|
|
|
"storj.io/storj/pkg/overlay"
|
|
|
|
"storj.io/storj/pkg/pb"
|
|
|
|
"storj.io/storj/pkg/provider"
|
|
|
|
"storj.io/storj/pkg/utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2019-01-23 19:58:44 +00:00
|
|
|
mon = monkit.Package()
|
|
|
|
// Error is the main payments error class for this package
|
|
|
|
Error = errs.Class("payments server error: ")
|
2019-01-17 18:34:13 +00:00
|
|
|
)
|
|
|
|
|
2019-01-24 18:44:14 +00:00
|
|
|
// Config is a configuration struct for everything you need to start the Payments responsibility.
|
2019-01-23 19:58:44 +00:00
|
|
|
type Config struct {
|
|
|
|
Filepath string `help:"the file path of the generated csv" default:"$CONFDIR/payments"`
|
|
|
|
// TODO: service should not write to disk, but return the result instead
|
|
|
|
}
|
|
|
|
|
2019-01-24 18:44:14 +00:00
|
|
|
// Server holds references to info needed for the payments responsibility
|
|
|
|
type Server struct { // TODO: separate endpoint and service
|
2019-01-17 18:34:13 +00:00
|
|
|
filepath string
|
|
|
|
accountingDB accounting.DB
|
|
|
|
overlayDB overlay.DB
|
|
|
|
log *zap.Logger
|
2019-01-23 19:58:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new payments Endpoint
|
|
|
|
func New(log *zap.Logger, accounting accounting.DB, overlay overlay.DB, filepath string) *Server {
|
|
|
|
return &Server{
|
|
|
|
filepath: filepath,
|
|
|
|
accountingDB: accounting,
|
|
|
|
overlayDB: overlay,
|
|
|
|
log: log,
|
|
|
|
}
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Pay creates a payment to a single storage node
|
|
|
|
func (srv *Server) Pay(ctx context.Context, req *pb.PaymentRequest) (*pb.PaymentResponse, error) {
|
|
|
|
// TODO
|
2019-01-23 19:58:44 +00:00
|
|
|
return &pb.PaymentResponse{}, Error.New("Pay not implemented")
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate determines the outstanding balance for a given storage node
|
|
|
|
func (srv *Server) Calculate(ctx context.Context, req *pb.CalculateRequest) (*pb.CalculateResponse, error) {
|
|
|
|
// TODO
|
2019-01-23 19:58:44 +00:00
|
|
|
return &pb.CalculateResponse{}, Error.New("Calculate not implemented")
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AdjustPrices sets the prices paid by a satellite for data at rest and bandwidth
|
|
|
|
func (srv *Server) AdjustPrices(ctx context.Context, req *pb.AdjustPricesRequest) (*pb.AdjustPricesResponse, error) {
|
|
|
|
// TODO
|
2019-01-23 19:58:44 +00:00
|
|
|
return &pb.AdjustPricesResponse{}, Error.New("AdjustPrices not implemented")
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GenerateCSV creates a csv file for payment purposes
|
2019-01-23 19:58:44 +00:00
|
|
|
func (srv *Server) GenerateCSV(ctx context.Context, req *pb.GenerateCSVRequest) (_ *pb.GenerateCSVResponse, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-01-17 18:34:13 +00:00
|
|
|
start, err := ptypes.Timestamp(req.StartTime)
|
|
|
|
if err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
end, err := ptypes.Timestamp(req.EndTime)
|
|
|
|
if err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pi, err := provider.PeerIdentityFromContext(ctx)
|
|
|
|
if err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
layout := "2006-01-02"
|
|
|
|
|
|
|
|
if err := os.MkdirAll(srv.filepath, 0700); err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
filename := pi.ID.String() + "--" + start.Format(layout) + "--" + end.Format(layout) + ".csv"
|
|
|
|
path := filepath.Join(srv.filepath, filename)
|
|
|
|
file, err := os.Create(path)
|
|
|
|
if err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
defer utils.LogClose(file)
|
|
|
|
|
|
|
|
rows, err := srv.accountingDB.QueryPaymentInfo(ctx, start, end)
|
|
|
|
if err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
w := csv.NewWriter(file)
|
|
|
|
headers := []string{
|
|
|
|
"nodeID",
|
|
|
|
"nodeCreationDate",
|
|
|
|
"auditSuccessRatio",
|
2019-01-24 18:44:14 +00:00
|
|
|
"byte-hours:AtRest",
|
2019-01-17 19:39:32 +00:00
|
|
|
"bytes:BWRepair-GET",
|
|
|
|
"bytes:BWRepair-PUT",
|
|
|
|
"bytes:BWAudit",
|
|
|
|
"bytes:BWGet",
|
|
|
|
"bytes:BWPut",
|
2019-01-17 18:34:13 +00:00
|
|
|
"date",
|
|
|
|
"walletAddress",
|
|
|
|
}
|
|
|
|
if err := w.Write(headers); err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, row := range rows {
|
|
|
|
nid := row.NodeID
|
|
|
|
wallet, err := srv.overlayDB.GetWalletAddress(ctx, nid)
|
|
|
|
if err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
row.Wallet = wallet
|
|
|
|
record := structToStringSlice(row)
|
|
|
|
if err := w.Write(record); err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := w.Error(); err != nil {
|
2019-01-23 19:58:44 +00:00
|
|
|
return nil, Error.Wrap(err)
|
2019-01-17 18:34:13 +00:00
|
|
|
}
|
|
|
|
w.Flush()
|
|
|
|
abs, err := filepath.Abs(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &pb.GenerateCSVResponse{Filepath: abs}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func structToStringSlice(s *accounting.CSVRow) []string {
|
|
|
|
record := []string{
|
|
|
|
s.NodeID.String(),
|
|
|
|
s.NodeCreationDate.Format("2006-01-02"),
|
|
|
|
strconv.FormatFloat(s.AuditSuccessRatio, 'f', 5, 64),
|
|
|
|
strconv.FormatFloat(s.AtRestTotal, 'f', 5, 64),
|
|
|
|
strconv.FormatInt(s.GetRepairTotal, 10),
|
|
|
|
strconv.FormatInt(s.PutRepairTotal, 10),
|
|
|
|
strconv.FormatInt(s.GetAuditTotal, 10),
|
|
|
|
strconv.FormatInt(s.PutTotal, 10),
|
|
|
|
strconv.FormatInt(s.GetTotal, 10),
|
|
|
|
s.Date.Format("2006-01-02"),
|
|
|
|
s.Wallet,
|
|
|
|
}
|
|
|
|
return record
|
|
|
|
}
|