diff --git a/pkg/innerring/processors/settlement/basic/collect.go b/pkg/innerring/processors/settlement/basic/collect.go index 2c978c9c2..e9dc61aca 100644 --- a/pkg/innerring/processors/settlement/basic/collect.go +++ b/pkg/innerring/processors/settlement/basic/collect.go @@ -56,6 +56,11 @@ func (inc *IncomeSettlementContext) Collect() { avg := inc.avgEstimation(cnrEstimations[i]) // average container size per node total := calculateBasicSum(avg, cachedRate, len(cnrNodes)) + // fill distribute asset table + for i := range cnrNodes { + inc.distributeTable.Put(cnrNodes[i].PublicKey(), avg) + } + inc.txTable.Transfer(&common.TransferTx{ From: owner.Owner(), To: inc.bankOwner, diff --git a/pkg/innerring/processors/settlement/basic/context.go b/pkg/innerring/processors/settlement/basic/context.go index 6528fed3c..599effb1a 100644 --- a/pkg/innerring/processors/settlement/basic/context.go +++ b/pkg/innerring/processors/settlement/basic/context.go @@ -39,9 +39,13 @@ type ( container common.ContainerStorage placement common.PlacementCalculator exchange common.Exchanger + accounts common.AccountStorage txTable *common.TransferTable bankOwner *owner.ID + + // this table is not thread safe, make sure you use it with mu.Lock() + distributeTable *NodeSizeTable } IncomeSettlementContextPrms struct { @@ -53,6 +57,7 @@ type ( Container common.ContainerStorage Placement common.PlacementCalculator Exchange common.Exchanger + Accounts common.AccountStorage } ) @@ -63,16 +68,18 @@ func NewIncomeSettlementContext(p *IncomeSettlementContextPrms) (*IncomeSettleme } return &IncomeSettlementContext{ - log: p.Log, - epoch: p.Epoch, - rate: p.Rate, - estimations: p.Estimations, - balances: p.Balances, - container: p.Container, - placement: p.Placement, - exchange: p.Exchange, - txTable: common.NewTransferTable(), - bankOwner: bankingAccount, + log: p.Log, + epoch: p.Epoch, + rate: p.Rate, + estimations: p.Estimations, + balances: p.Balances, + container: p.Container, + placement: p.Placement, + exchange: p.Exchange, + accounts: p.Accounts, + txTable: common.NewTransferTable(), + bankOwner: bankingAccount, + distributeTable: NewNodeSizeTable(), }, nil } diff --git a/pkg/innerring/processors/settlement/basic/distribute.go b/pkg/innerring/processors/settlement/basic/distribute.go index 70eb13d2f..32e9ff045 100644 --- a/pkg/innerring/processors/settlement/basic/distribute.go +++ b/pkg/innerring/processors/settlement/basic/distribute.go @@ -1,6 +1,10 @@ package basic import ( + "encoding/hex" + "math/big" + + "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/common" "go.uber.org/zap" ) @@ -8,6 +12,8 @@ func (inc *IncomeSettlementContext) Distribute() { inc.mu.Lock() defer inc.mu.Unlock() + txTable := common.NewTransferTable() + bankBalance, err := inc.balances.Balance(inc.bankOwner) if err != nil { inc.log.Error("can't fetch balance of banking account", @@ -16,5 +22,33 @@ func (inc *IncomeSettlementContext) Distribute() { return } - _ = bankBalance + total := inc.distributeTable.Total() + + inc.distributeTable.Iterate(func(key []byte, n *big.Int) { + nodeOwner, err := inc.accounts.ResolveKey(nodeInfoWrapper(key)) + if err != nil { + inc.log.Warn("can't transform public key to owner id", + zap.String("public_key", hex.EncodeToString(key)), + zap.String("error", err.Error())) + + return + } + + txTable.Transfer(&common.TransferTx{ + From: inc.bankOwner, + To: nodeOwner, + Amount: normalizedValue(n, total, bankBalance), + }) + }) + + common.TransferAssets(inc.exchange, txTable) +} + +func normalizedValue(n, total, limit *big.Int) *big.Int { + if limit.Cmp(bigZero) == 0 { + return new(big.Int) + } + + n.Mul(n, limit) + return n.Div(n, total) } diff --git a/pkg/innerring/processors/settlement/basic/distribute_test.go b/pkg/innerring/processors/settlement/basic/distribute_test.go new file mode 100644 index 000000000..959ad7f9b --- /dev/null +++ b/pkg/innerring/processors/settlement/basic/distribute_test.go @@ -0,0 +1,54 @@ +package basic + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +type normalizedValueCase struct { + name string + n, total, limit uint64 + expected uint64 +} + +func TestNormalizedValues(t *testing.T) { + testCases := []normalizedValueCase{ + { + name: "zero limit", + n: 50, + total: 100, + limit: 0, + expected: 0, + }, + { + name: "scale down", + n: 50, + total: 100, + limit: 10, + expected: 5, + }, + { + name: "scale up", + n: 50, + total: 100, + limit: 1000, + expected: 500, + }, + } + + for _, testCase := range testCases { + testNormalizedValues(t, testCase) + } +} + +func testNormalizedValues(t *testing.T, c normalizedValueCase) { + n := new(big.Int).SetUint64(c.n) + total := new(big.Int).SetUint64(c.total) + limit := new(big.Int).SetUint64(c.limit) + exp := new(big.Int).SetUint64(c.expected) + + got := normalizedValue(n, total, limit) + require.Zero(t, exp.Cmp(got), c.name) +} diff --git a/pkg/innerring/processors/settlement/basic/util.go b/pkg/innerring/processors/settlement/basic/util.go new file mode 100644 index 000000000..64ee193cf --- /dev/null +++ b/pkg/innerring/processors/settlement/basic/util.go @@ -0,0 +1,44 @@ +package basic + +import ( + "math/big" +) + +// NodeSizeTable is not thread safe, make sure it is accessed with external +// locks or in single routine. +type NodeSizeTable struct { + prices map[string]uint64 + total uint64 +} + +func (t *NodeSizeTable) Put(id []byte, avg uint64) { + t.prices[string(id)] += avg + t.total += avg +} + +func (t *NodeSizeTable) Total() *big.Int { + return new(big.Int).SetUint64(t.total) +} + +func (t *NodeSizeTable) Iterate(f func([]byte, *big.Int)) { + for k, v := range t.prices { + n := new(big.Int).SetUint64(v) + f([]byte(k), n) + } +} + +func NewNodeSizeTable() *NodeSizeTable { + return &NodeSizeTable{ + prices: make(map[string]uint64), + } +} + +type nodeInfoWrapper []byte + +func (nodeInfoWrapper) Price() *big.Int { + panic("should not be used") +} + +func (n nodeInfoWrapper) PublicKey() []byte { + return n +}