From f45675b7a26d703dad6e4168fe5e6a85cb2ab122 Mon Sep 17 00:00:00 2001
From: Alex Vanin <alexey@nspcc.ru>
Date: Mon, 1 Feb 2021 15:40:07 +0300
Subject: [PATCH] [#360] Share common parts of basic and audit settlements

Signed-off-by: Alex Vanin <alexey@nspcc.ru>
---
 pkg/innerring/innerring.go                    |  6 +-
 .../processors/settlement/audit/calculate.go  | 36 +++------
 .../processors/settlement/audit/prm.go        | 59 ++-------------
 .../processors/settlement/audit/util.go       | 57 ---------------
 .../processors/settlement/common/types.go     | 54 ++++++++++++++
 .../processors/settlement/common/util.go      | 73 +++++++++++++++++++
 pkg/innerring/{audit.go => settlement.go}     | 53 ++++++++------
 7 files changed, 180 insertions(+), 158 deletions(-)
 delete mode 100644 pkg/innerring/processors/settlement/audit/util.go
 create mode 100644 pkg/innerring/processors/settlement/common/types.go
 create mode 100644 pkg/innerring/processors/settlement/common/util.go
 rename pkg/innerring/{audit.go => settlement.go} (71%)

diff --git a/pkg/innerring/innerring.go b/pkg/innerring/innerring.go
index cc1041a6..b6a48766 100644
--- a/pkg/innerring/innerring.go
+++ b/pkg/innerring/innerring.go
@@ -284,7 +284,7 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper) (*Server, error
 	}
 
 	// create settlement processor dependencies
-	auditCalcDeps := &auditSettlementDeps{
+	settlementDeps := &settlementDeps{
 		log:           server.log,
 		cnrSrc:        cnrClient,
 		auditClient:   server.auditClient,
@@ -293,6 +293,10 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper) (*Server, error
 		balanceClient: balClient,
 	}
 
+	auditCalcDeps := &auditSettlementDeps{
+		settlementDeps: settlementDeps,
+	}
+
 	auditSettlementCalc := auditSettlement.NewCalculator(
 		&auditSettlement.CalculatorPrm{
 			ResultStorage:       auditCalcDeps,
diff --git a/pkg/innerring/processors/settlement/audit/calculate.go b/pkg/innerring/processors/settlement/audit/calculate.go
index 0b5f5de6..043dd12a 100644
--- a/pkg/innerring/processors/settlement/audit/calculate.go
+++ b/pkg/innerring/processors/settlement/audit/calculate.go
@@ -8,6 +8,7 @@ import (
 	"github.com/nspcc-dev/neofs-api-go/pkg/audit"
 	"github.com/nspcc-dev/neofs-api-go/pkg/container"
 	"github.com/nspcc-dev/neofs-api-go/pkg/object"
+	"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/common"
 	"github.com/nspcc-dev/neofs-node/pkg/util/logger"
 	"go.uber.org/zap"
 )
@@ -28,13 +29,13 @@ type singleResultCtx struct {
 
 	cid *container.ID
 
-	txTable *transferTable
+	txTable *common.TransferTable
 
-	cnrInfo ContainerInfo
+	cnrInfo common.ContainerInfo
 
-	cnrNodes []NodeInfo
+	cnrNodes []common.NodeInfo
 
-	passNodes map[string]NodeInfo
+	passNodes map[string]common.NodeInfo
 
 	sumSGSize *big.Int
 }
@@ -69,7 +70,7 @@ func (c *Calculator) Calculate(p *CalculatePrm) {
 		zap.Int("number", len(auditResults)),
 	)
 
-	table := newTransferTable()
+	table := common.NewTransferTable()
 
 	for i := range auditResults {
 		c.processResult(&singleResultCtx{
@@ -81,20 +82,7 @@ func (c *Calculator) Calculate(p *CalculatePrm) {
 
 	log.Debug("processing transfers")
 
-	table.iterate(func(tx *transferTx) {
-		sign := tx.amount.Sign()
-		if sign == 0 {
-			log.Debug("ignore zero transfer")
-			return
-		}
-
-		if sign < 0 {
-			tx.from, tx.to = tx.to, tx.from
-			tx.amount.Neg(tx.amount)
-		}
-
-		c.prm.Exchanger.Transfer(tx.from, tx.to, tx.amount)
-	})
+	common.TransferAssets(c.prm.Exchanger, table)
 }
 
 func (c *Calculator) processResult(ctx *singleResultCtx) {
@@ -168,7 +156,7 @@ func (c *Calculator) buildPlacement(ctx *singleResultCtx) bool {
 }
 
 func (c *Calculator) collectPassNodes(ctx *singleResultCtx) bool {
-	ctx.passNodes = make(map[string]NodeInfo)
+	ctx.passNodes = make(map[string]common.NodeInfo)
 
 loop:
 	for _, cnrNode := range ctx.cnrNodes {
@@ -261,10 +249,10 @@ func (c *Calculator) fillTransferTable(ctx *singleResultCtx) bool {
 			fee.Add(fee, bigOne)
 		}
 
-		ctx.txTable.transfer(&transferTx{
-			from:   cnrOwner,
-			to:     ownerID,
-			amount: fee,
+		ctx.txTable.Transfer(&common.TransferTx{
+			From:   cnrOwner,
+			To:     ownerID,
+			Amount: fee,
 		})
 	}
 
diff --git a/pkg/innerring/processors/settlement/audit/prm.go b/pkg/innerring/processors/settlement/audit/prm.go
index 379396d9..7ede9252 100644
--- a/pkg/innerring/processors/settlement/audit/prm.go
+++ b/pkg/innerring/processors/settlement/audit/prm.go
@@ -1,27 +1,24 @@
 package audit
 
 import (
-	"math/big"
-
 	"github.com/nspcc-dev/neofs-api-go/pkg/audit"
-	"github.com/nspcc-dev/neofs-api-go/pkg/container"
 	"github.com/nspcc-dev/neofs-api-go/pkg/object"
-	"github.com/nspcc-dev/neofs-api-go/pkg/owner"
+	"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/common"
 )
 
 // CalculatorPrm groups the parameters of Calculator's constructor.
 type CalculatorPrm struct {
 	ResultStorage ResultStorage
 
-	ContainerStorage ContainerStorage
+	ContainerStorage common.ContainerStorage
 
-	PlacementCalculator PlacementCalculator
+	PlacementCalculator common.PlacementCalculator
 
 	SGStorage SGStorage
 
-	AccountStorage AccountStorage
+	AccountStorage common.AccountStorage
 
-	Exchanger Exchanger
+	Exchanger common.Exchanger
 }
 
 // ResultStorage is an interface of storage of the audit results.
@@ -30,37 +27,6 @@ type ResultStorage interface {
 	AuditResultsForEpoch(epoch uint64) ([]*audit.Result, error)
 }
 
-// NodeInfo groups the data about the storage node
-// necessary for calculating audit fees.
-type NodeInfo interface {
-	// Must return storage price of the node for one epoch in GASe-12.
-	Price() *big.Int
-
-	// Must return public key of the node.
-	PublicKey() []byte
-}
-
-// ContainerInfo groups the data about NeoFS container
-// necessary for calculating audit fee.
-type ContainerInfo interface {
-	// Must return identifier of the container owner.
-	Owner() *owner.ID
-}
-
-// ContainerStorage is an interface of
-// storage of the NeoFS containers.
-type ContainerStorage interface {
-	// Must return information about the container by ID.
-	ContainerInfo(*container.ID) (ContainerInfo, error)
-}
-
-// PlacementCalculator is a component interface
-// that builds placement vectors.
-type PlacementCalculator interface {
-	// Must return information about the nodes from container cid of the epoch e.
-	ContainerNodes(e uint64, cid *container.ID) ([]NodeInfo, error)
-}
-
 // SGInfo groups the data about NeoFS storage group
 // necessary for calculating audit fee.
 type SGInfo interface {
@@ -73,18 +39,3 @@ type SGStorage interface {
 	// Must return information about the storage group by address.
 	SGInfo(*object.Address) (SGInfo, error)
 }
-
-// AccountStorage is an network member accounts interface.
-type AccountStorage interface {
-	// Must resolve information about the storage node
-	// to its ID in system.
-	ResolveKey(NodeInfo) (*owner.ID, error)
-}
-
-// Exchanger is an interface of monetary component.
-type Exchanger interface {
-	// Must transfer amount of GASe-12 from sender to recipient.
-	//
-	// Amount must be positive.
-	Transfer(sender, recipient *owner.ID, amount *big.Int)
-}
diff --git a/pkg/innerring/processors/settlement/audit/util.go b/pkg/innerring/processors/settlement/audit/util.go
deleted file mode 100644
index b354d56f..00000000
--- a/pkg/innerring/processors/settlement/audit/util.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package audit
-
-import (
-	"math/big"
-
-	"github.com/nspcc-dev/neofs-api-go/pkg/owner"
-)
-
-type transferTable struct {
-	txs map[string]map[string]*transferTx
-}
-
-type transferTx struct {
-	from, to *owner.ID
-
-	amount *big.Int
-}
-
-func newTransferTable() *transferTable {
-	return &transferTable{
-		txs: make(map[string]map[string]*transferTx),
-	}
-}
-
-func (t *transferTable) transfer(tx *transferTx) {
-	from, to := tx.from.String(), tx.to.String()
-	if from == to {
-		return
-	}
-
-	m, ok := t.txs[from]
-	if !ok {
-		if m, ok = t.txs[to]; ok {
-			to = from // ignore `from = to` swap because `from` doesn't require
-			tx.amount.Neg(tx.amount)
-		} else {
-			m = make(map[string]*transferTx, 1)
-			t.txs[from] = m
-		}
-	}
-
-	tgt, ok := m[to]
-	if !ok {
-		m[to] = tx
-		return
-	}
-
-	tgt.amount.Add(tgt.amount, tx.amount)
-}
-
-func (t *transferTable) iterate(f func(*transferTx)) {
-	for _, m := range t.txs {
-		for _, tx := range m {
-			f(tx)
-		}
-	}
-}
diff --git a/pkg/innerring/processors/settlement/common/types.go b/pkg/innerring/processors/settlement/common/types.go
new file mode 100644
index 00000000..fb245676
--- /dev/null
+++ b/pkg/innerring/processors/settlement/common/types.go
@@ -0,0 +1,54 @@
+package common
+
+import (
+	"math/big"
+
+	"github.com/nspcc-dev/neofs-api-go/pkg/container"
+	"github.com/nspcc-dev/neofs-api-go/pkg/owner"
+)
+
+// NodeInfo groups the data about the storage node
+// necessary for calculating audit fees.
+type NodeInfo interface {
+	// Must return storage price of the node for one epoch in GASe-12.
+	Price() *big.Int
+
+	// Must return public key of the node.
+	PublicKey() []byte
+}
+
+// ContainerInfo groups the data about NeoFS container
+// necessary for calculating audit fee.
+type ContainerInfo interface {
+	// Must return identifier of the container owner.
+	Owner() *owner.ID
+}
+
+// ContainerStorage is an interface of
+// storage of the NeoFS containers.
+type ContainerStorage interface {
+	// Must return information about the container by ID.
+	ContainerInfo(*container.ID) (ContainerInfo, error)
+}
+
+// PlacementCalculator is a component interface
+// that builds placement vectors.
+type PlacementCalculator interface {
+	// Must return information about the nodes from container cid of the epoch e.
+	ContainerNodes(e uint64, cid *container.ID) ([]NodeInfo, error)
+}
+
+// AccountStorage is an network member accounts interface.
+type AccountStorage interface {
+	// Must resolve information about the storage node
+	// to its ID in system.
+	ResolveKey(NodeInfo) (*owner.ID, error)
+}
+
+// Exchanger is an interface of monetary component.
+type Exchanger interface {
+	// Must transfer amount of GASe-12 from sender to recipient.
+	//
+	// Amount must be positive.
+	Transfer(sender, recipient *owner.ID, amount *big.Int)
+}
diff --git a/pkg/innerring/processors/settlement/common/util.go b/pkg/innerring/processors/settlement/common/util.go
new file mode 100644
index 00000000..39bd7840
--- /dev/null
+++ b/pkg/innerring/processors/settlement/common/util.go
@@ -0,0 +1,73 @@
+package common
+
+import (
+	"math/big"
+
+	"github.com/nspcc-dev/neofs-api-go/pkg/owner"
+)
+
+type TransferTable struct {
+	txs map[string]map[string]*TransferTx
+}
+
+type TransferTx struct {
+	From, To *owner.ID
+
+	Amount *big.Int
+}
+
+func NewTransferTable() *TransferTable {
+	return &TransferTable{
+		txs: make(map[string]map[string]*TransferTx),
+	}
+}
+
+func (t *TransferTable) Transfer(tx *TransferTx) {
+	from, to := tx.From.String(), tx.To.String()
+	if from == to {
+		return
+	}
+
+	m, ok := t.txs[from]
+	if !ok {
+		if m, ok = t.txs[to]; ok {
+			to = from // ignore `From = To` swap because `From` doesn't require
+			tx.Amount.Neg(tx.Amount)
+		} else {
+			m = make(map[string]*TransferTx, 1)
+			t.txs[from] = m
+		}
+	}
+
+	tgt, ok := m[to]
+	if !ok {
+		m[to] = tx
+		return
+	}
+
+	tgt.Amount.Add(tgt.Amount, tx.Amount)
+}
+
+func (t *TransferTable) Iterate(f func(*TransferTx)) {
+	for _, m := range t.txs {
+		for _, tx := range m {
+			f(tx)
+		}
+	}
+}
+
+func TransferAssets(e Exchanger, t *TransferTable) {
+	t.Iterate(func(tx *TransferTx) {
+		sign := tx.Amount.Sign()
+		if sign == 0 {
+			return
+		}
+
+		if sign < 0 {
+			tx.From, tx.To = tx.To, tx.From
+			tx.Amount.Neg(tx.Amount)
+		}
+
+		e.Transfer(tx.From, tx.To, tx.Amount)
+	})
+}
diff --git a/pkg/innerring/audit.go b/pkg/innerring/settlement.go
similarity index 71%
rename from pkg/innerring/audit.go
rename to pkg/innerring/settlement.go
index f32b0fe9..d71b4f17 100644
--- a/pkg/innerring/audit.go
+++ b/pkg/innerring/settlement.go
@@ -14,6 +14,7 @@ import (
 	"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/common"
 	auditClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/audit/wrapper"
 	balanceClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/balance/wrapper"
 	"github.com/nspcc-dev/neofs-node/pkg/util/logger"
@@ -21,7 +22,7 @@ import (
 	"go.uber.org/zap"
 )
 
-type auditSettlementDeps struct {
+type settlementDeps struct {
 	log *logger.Logger
 
 	cnrSrc container.Source
@@ -35,6 +36,10 @@ type auditSettlementDeps struct {
 	balanceClient *balanceClient.Wrapper
 }
 
+type auditSettlementDeps struct {
+	*settlementDeps
+}
+
 type auditSettlementCalculator audit.Calculator
 
 type containerWrapper containerAPI.Container
@@ -61,8 +66,8 @@ func (c *containerWrapper) Owner() *owner.ID {
 	return (*containerAPI.Container)(c).OwnerID()
 }
 
-func (a auditSettlementDeps) AuditResultsForEpoch(epoch uint64) ([]*auditAPI.Result, error) {
-	idList, err := a.auditClient.ListAuditResultIDByEpoch(epoch)
+func (s settlementDeps) AuditResultsForEpoch(epoch uint64) ([]*auditAPI.Result, error) {
+	idList, err := s.auditClient.ListAuditResultIDByEpoch(epoch)
 	if err != nil {
 		return nil, errors.Wrap(err, "could not list audit results in sidechain")
 	}
@@ -70,7 +75,7 @@ func (a auditSettlementDeps) AuditResultsForEpoch(epoch uint64) ([]*auditAPI.Res
 	res := make([]*auditAPI.Result, 0, len(idList))
 
 	for i := range idList {
-		r, err := a.auditClient.GetAuditResult(idList[i])
+		r, err := s.auditClient.GetAuditResult(idList[i])
 		if err != nil {
 			return nil, errors.Wrap(err, "could not get audit result")
 		}
@@ -81,8 +86,8 @@ func (a auditSettlementDeps) AuditResultsForEpoch(epoch uint64) ([]*auditAPI.Res
 	return res, nil
 }
 
-func (a auditSettlementDeps) ContainerInfo(cid *containerAPI.ID) (audit.ContainerInfo, error) {
-	cnr, err := a.cnrSrc.Get(cid)
+func (s settlementDeps) ContainerInfo(cid *containerAPI.ID) (common.ContainerInfo, error) {
+	cnr, err := s.cnrSrc.Get(cid)
 	if err != nil {
 		return nil, errors.Wrap(err, "could not get container from storage")
 	}
@@ -90,23 +95,23 @@ func (a auditSettlementDeps) ContainerInfo(cid *containerAPI.ID) (audit.Containe
 	return (*containerWrapper)(cnr), nil
 }
 
-func (a auditSettlementDeps) buildContainer(e uint64, cid *containerAPI.ID) (netmapAPI.ContainerNodes, *netmapAPI.Netmap, error) {
+func (s settlementDeps) buildContainer(e uint64, cid *containerAPI.ID) (netmapAPI.ContainerNodes, *netmapAPI.Netmap, error) {
 	var (
 		nm  *netmapAPI.Netmap
 		err error
 	)
 
 	if e > 0 {
-		nm, err = a.nmSrc.GetNetMapByEpoch(e)
+		nm, err = s.nmSrc.GetNetMapByEpoch(e)
 	} else {
-		nm, err = netmap.GetLatestNetworkMap(a.nmSrc)
+		nm, err = netmap.GetLatestNetworkMap(s.nmSrc)
 	}
 
 	if err != nil {
 		return nil, nil, errors.Wrap(err, "could not get network map from storage")
 	}
 
-	cnr, err := a.cnrSrc.Get(cid)
+	cnr, err := s.cnrSrc.Get(cid)
 	if err != nil {
 		return nil, nil, errors.Wrap(err, "could not get container from sidechain")
 	}
@@ -122,14 +127,14 @@ func (a auditSettlementDeps) buildContainer(e uint64, cid *containerAPI.ID) (net
 	return cn, nm, nil
 }
 
-func (a auditSettlementDeps) ContainerNodes(e uint64, cid *containerAPI.ID) ([]audit.NodeInfo, error) {
-	cn, _, err := a.buildContainer(e, cid)
+func (s settlementDeps) ContainerNodes(e uint64, cid *containerAPI.ID) ([]common.NodeInfo, error) {
+	cn, _, err := s.buildContainer(e, cid)
 	if err != nil {
 		return nil, err
 	}
 
 	ns := cn.Flatten()
-	res := make([]audit.NodeInfo, 0, len(ns))
+	res := make([]common.NodeInfo, 0, len(ns))
 
 	for i := range ns {
 		res = append(res, &nodeInfoWrapper{
@@ -140,13 +145,13 @@ func (a auditSettlementDeps) ContainerNodes(e uint64, cid *containerAPI.ID) ([]a
 	return res, nil
 }
 
-func (a auditSettlementDeps) SGInfo(addr *object.Address) (audit.SGInfo, error) {
-	cn, nm, err := a.buildContainer(0, addr.ContainerID())
+func (s settlementDeps) SGInfo(addr *object.Address) (audit.SGInfo, error) {
+	cn, nm, err := s.buildContainer(0, addr.ContainerID())
 	if err != nil {
 		return nil, err
 	}
 
-	sg, err := a.clientCache.getSG(context.Background(), addr, nm, cn)
+	sg, err := s.clientCache.getSG(context.Background(), addr, nm, cn)
 	if err != nil {
 		return nil, err
 	}
@@ -154,7 +159,7 @@ func (a auditSettlementDeps) SGInfo(addr *object.Address) (audit.SGInfo, error)
 	return (*sgWrapper)(sg), nil
 }
 
-func (a auditSettlementDeps) ResolveKey(ni audit.NodeInfo) (*owner.ID, error) {
+func (s settlementDeps) ResolveKey(ni common.NodeInfo) (*owner.ID, error) {
 	w, err := owner.NEO3WalletFromPublicKey(crypto.UnmarshalPublicKey(ni.PublicKey()))
 	if err != nil {
 		return nil, err
@@ -168,24 +173,24 @@ func (a auditSettlementDeps) ResolveKey(ni audit.NodeInfo) (*owner.ID, error) {
 
 var transferAuditDetails = []byte("settlement-audit")
 
-func (a auditSettlementDeps) Transfer(sender, recipient *owner.ID, amount *big.Int) {
-	log := a.log.With(
+func (s settlementDeps) transfer(sender, recipient *owner.ID, amount *big.Int, details []byte) {
+	log := s.log.With(
 		zap.Stringer("sender", sender),
 		zap.Stringer("recipient", recipient),
 		zap.Stringer("amount (GASe-12)", amount),
 	)
 
 	if !amount.IsInt64() {
-		a.log.Error("amount can not be represented as an int64")
+		s.log.Error("amount can not be represented as an int64")
 
 		return
 	}
 
-	if err := a.balanceClient.TransferX(balanceClient.TransferPrm{
+	if err := s.balanceClient.TransferX(balanceClient.TransferPrm{
 		Amount:  amount.Int64(),
 		From:    sender,
 		To:      recipient,
-		Details: transferAuditDetails,
+		Details: details,
 	}); err != nil {
 		log.Error("could not send transfer transaction for audit",
 			zap.String("error", err.Error()),
@@ -197,6 +202,10 @@ func (a auditSettlementDeps) Transfer(sender, recipient *owner.ID, amount *big.I
 	log.Debug("transfer transaction for audit was successfully sent")
 }
 
+func (a auditSettlementDeps) Transfer(sender, recipient *owner.ID, amount *big.Int) {
+	a.transfer(sender, recipient, amount, transferAuditDetails)
+}
+
 func (s *auditSettlementCalculator) ProcessAuditSettlements(epoch uint64) {
 	(*audit.Calculator)(s).Calculate(&audit.CalculatePrm{
 		Epoch: epoch,