frostfs-node/pkg/innerring/settlement.go

301 lines
7.2 KiB
Go

package innerring
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"encoding/hex"
"fmt"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-node/pkg/core/container"
"github.com/nspcc-dev/neofs-node/pkg/core/netmap"
"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/audit"
"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/basic"
"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/common"
auditClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/audit"
balanceClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/balance"
containerClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/container"
netmapClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
auditAPI "github.com/nspcc-dev/neofs-sdk-go/audit"
containerAPI "github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
netmapAPI "github.com/nspcc-dev/neofs-sdk-go/netmap"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
"github.com/nspcc-dev/neofs-sdk-go/storagegroup"
"github.com/nspcc-dev/neofs-sdk-go/user"
"go.uber.org/zap"
)
const (
auditSettlementContext = "audit"
basicIncomeSettlementContext = "basic income"
)
type settlementDeps struct {
log *logger.Logger
cnrSrc container.Source
auditClient *auditClient.Client
nmClient *netmapClient.Client
clientCache *ClientCache
balanceClient *balanceClient.Client
settlementCtx string
}
type auditSettlementDeps struct {
settlementDeps
}
type basicIncomeSettlementDeps struct {
settlementDeps
cnrClient *containerClient.Client
}
type basicSettlementConstructor struct {
dep *basicIncomeSettlementDeps
}
type auditSettlementCalculator audit.Calculator
type containerWrapper containerAPI.Container
type nodeInfoWrapper struct {
ni netmapAPI.NodeInfo
}
type sgWrapper storagegroup.StorageGroup
func (s *sgWrapper) Size() uint64 {
return (*storagegroup.StorageGroup)(s).ValidationDataSize()
}
func (n nodeInfoWrapper) PublicKey() []byte {
return n.ni.PublicKey()
}
func (n nodeInfoWrapper) Price() *big.Int {
return big.NewInt(int64(n.ni.Price()))
}
func (c containerWrapper) Owner() user.ID {
return (containerAPI.Container)(c).Owner()
}
func (s settlementDeps) AuditResultsForEpoch(epoch uint64) ([]*auditAPI.Result, error) {
idList, err := s.auditClient.ListAuditResultIDByEpoch(epoch)
if err != nil {
return nil, fmt.Errorf("could not list audit results in sidechain: %w", err)
}
res := make([]*auditAPI.Result, 0, len(idList))
for i := range idList {
r, err := s.auditClient.GetAuditResult(idList[i])
if err != nil {
return nil, fmt.Errorf("could not get audit result: %w", err)
}
res = append(res, r)
}
return res, nil
}
func (s settlementDeps) ContainerInfo(cid cid.ID) (common.ContainerInfo, error) {
cnr, err := s.cnrSrc.Get(cid)
if err != nil {
return nil, fmt.Errorf("could not get container from storage: %w", err)
}
return (containerWrapper)(cnr.Value), nil
}
func (s settlementDeps) buildContainer(e uint64, cid cid.ID) ([][]netmapAPI.NodeInfo, *netmapAPI.NetMap, error) {
var (
nm *netmapAPI.NetMap
err error
)
if e > 0 {
nm, err = s.nmClient.GetNetMapByEpoch(e)
} else {
nm, err = netmap.GetLatestNetworkMap(s.nmClient)
}
if err != nil {
return nil, nil, fmt.Errorf("could not get network map from storage: %w", err)
}
cnr, err := s.cnrSrc.Get(cid)
if err != nil {
return nil, nil, fmt.Errorf("could not get container from sidechain: %w", err)
}
binCnr := make([]byte, sha256.Size)
cid.Encode(binCnr)
cn, err := nm.ContainerNodes(
cnr.Value.PlacementPolicy(),
binCnr, // may be replace pivot calculation to neofs-api-go
)
if err != nil {
return nil, nil, fmt.Errorf("could not calculate container nodes: %w", err)
}
return cn, nm, nil
}
func (s settlementDeps) ContainerNodes(e uint64, cid cid.ID) ([]common.NodeInfo, error) {
cn, _, err := s.buildContainer(e, cid)
if err != nil {
return nil, err
}
var sz int
for i := range cn {
sz += len(cn[i])
}
res := make([]common.NodeInfo, 0, sz)
for i := range cn {
for j := range cn[i] {
res = append(res, nodeInfoWrapper{
ni: cn[i][j],
})
}
}
return res, nil
}
// SGInfo returns audit.SGInfo by object address.
//
// Returns an error of type apistatus.ObjectNotFound if storage group is missing.
func (s settlementDeps) SGInfo(addr oid.Address) (audit.SGInfo, error) {
cnr := addr.Container()
cn, nm, err := s.buildContainer(0, cnr)
if err != nil {
return nil, err
}
sg, err := s.clientCache.getSG(context.Background(), addr, nm, cn)
if err != nil {
return nil, err
}
return (*sgWrapper)(sg), nil
}
func (s settlementDeps) ResolveKey(ni common.NodeInfo) (*user.ID, error) {
pub, err := keys.NewPublicKeyFromBytes(ni.PublicKey(), elliptic.P256())
if err != nil {
return nil, err
}
var id user.ID
user.IDFromKey(&id, (ecdsa.PublicKey)(*pub))
return &id, nil
}
func (s settlementDeps) Transfer(sender, recipient user.ID, amount *big.Int, details []byte) {
if s.settlementCtx == "" {
panic("unknown settlement deps context")
}
log := s.log.With(
zap.Stringer("sender", sender),
zap.Stringer("recipient", recipient),
zap.Stringer("amount (GASe-12)", amount),
zap.String("details", hex.EncodeToString(details)),
)
if !amount.IsInt64() {
s.log.Error("amount can not be represented as an int64")
return
}
params := balanceClient.TransferPrm{
Amount: amount.Int64(),
From: sender,
To: recipient,
Details: details,
}
err := s.balanceClient.TransferX(params)
if err != nil {
log.Error(fmt.Sprintf("%s: could not send transfer", s.settlementCtx),
zap.String("error", err.Error()),
)
return
}
log.Debug(fmt.Sprintf("%s: transfer was successfully sent", s.settlementCtx))
}
func (b basicIncomeSettlementDeps) BasicRate() (uint64, error) {
return b.nmClient.BasicIncomeRate()
}
func (b basicIncomeSettlementDeps) Estimations(epoch uint64) ([]*containerClient.Estimations, error) {
estimationIDs, err := b.cnrClient.ListLoadEstimationsByEpoch(epoch)
if err != nil {
return nil, err
}
result := make([]*containerClient.Estimations, 0, len(estimationIDs))
for i := range estimationIDs {
estimation, err := b.cnrClient.GetUsedSpaceEstimations(estimationIDs[i])
if err != nil {
b.log.Warn("can't get used space estimation",
zap.String("estimation_id", hex.EncodeToString(estimationIDs[i])),
zap.String("error", err.Error()))
continue
}
result = append(result, estimation)
}
return result, nil
}
func (b basicIncomeSettlementDeps) Balance(id user.ID) (*big.Int, error) {
return b.balanceClient.BalanceOf(id)
}
func (s *auditSettlementCalculator) ProcessAuditSettlements(epoch uint64) {
(*audit.Calculator)(s).Calculate(&audit.CalculatePrm{
Epoch: epoch,
})
}
func (b *basicSettlementConstructor) CreateContext(epoch uint64) (*basic.IncomeSettlementContext, error) {
return basic.NewIncomeSettlementContext(&basic.IncomeSettlementContextPrms{
Log: b.dep.log,
Epoch: epoch,
Rate: b.dep,
Estimations: b.dep,
Balances: b.dep,
Container: b.dep,
Placement: b.dep,
Exchange: b.dep,
Accounts: b.dep,
}), nil
}