From a624bb881dc615f4b8a18233f61ca95ac886d276 Mon Sep 17 00:00:00 2001 From: Alex Vanin Date: Mon, 1 Feb 2021 19:17:16 +0300 Subject: [PATCH] [#360] Implement basic settlement context Basic settlement context is a main structure that implement logic of basic settlement phases: collecting assets from container owners and then distributing them to storage nodes. Signed-off-by: Alex Vanin --- .../processors/settlement/basic/collect.go | 94 +++++++++++++++++++ .../processors/settlement/basic/context.go | 86 +++++++++++++++++ .../processors/settlement/basic/distribute.go | 6 ++ 3 files changed, 186 insertions(+) create mode 100644 pkg/innerring/processors/settlement/basic/collect.go create mode 100644 pkg/innerring/processors/settlement/basic/context.go create mode 100644 pkg/innerring/processors/settlement/basic/distribute.go diff --git a/pkg/innerring/processors/settlement/basic/collect.go b/pkg/innerring/processors/settlement/basic/collect.go new file mode 100644 index 000000000..fb72ace9f --- /dev/null +++ b/pkg/innerring/processors/settlement/basic/collect.go @@ -0,0 +1,94 @@ +package basic + +import ( + "math/big" + + "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/common" + "github.com/nspcc-dev/neofs-node/pkg/morph/client/container/wrapper" + "go.uber.org/zap" +) + +var ( + bigGB = big.NewInt(1 << 30) + bigZero = big.NewInt(0) + bigOne = big.NewInt(1) +) + +func (inc *IncomeSettlementContext) Collect() { + inc.mu.Lock() + defer inc.mu.Unlock() + + // todo: save state of bank wallet + + cachedRate := inc.rate.BasicRate() + + cnrEstimations, err := inc.estimations.Estimations(inc.epoch) + if err != nil { + inc.log.Warn("can't fetch container size estimations", + zap.Uint64("epoch", inc.epoch)) + + return + } + + for i := range cnrEstimations { + owner, err := inc.container.ContainerInfo(cnrEstimations[i].ContainerID) + if err != nil { + inc.log.Warn("can't fetch container info", + zap.Uint64("epoch", inc.epoch), + zap.Stringer("container_id", cnrEstimations[i].ContainerID)) + + continue + } + + cnrNodes, err := inc.placement.ContainerNodes(inc.epoch, cnrEstimations[i].ContainerID) + if err != nil { + inc.log.Debug("can't fetch container info", + zap.Uint64("epoch", inc.epoch), + zap.Stringer("container_id", cnrEstimations[i].ContainerID)) + + continue + } + + avg := inc.avgEstimation(cnrEstimations[i]) // average container size per node + total := calculateBasicSum(avg, cachedRate, len(cnrNodes)) + + inc.txTable.Transfer(&common.TransferTx{ + From: owner.Owner(), + To: inc.bankOwner, + Amount: total, + }) + } + + common.TransferAssets(inc.exchange, inc.txTable) +} + +// avgEstimation returns estimation value for single container. Right now it +// simply calculates average of all announcements, however it can be smarter and +// base result on reputation of announcers and clever math. +func (inc *IncomeSettlementContext) avgEstimation(e *wrapper.Estimations) (avg uint64) { + if len(e.Values) == 0 { + return 0 + } + + for i := range e.Values { + avg += e.Values[i].Size + } + + return avg / uint64(len(e.Values)) +} + +func calculateBasicSum(size, rate uint64, ln int) *big.Int { + bigRate := big.NewInt(int64(rate)) + + total := size * uint64(ln) + + price := new(big.Int).SetUint64(total) + price.Mul(price, bigRate) + price.Div(price, bigGB) + + if price.Cmp(bigZero) == 0 { + price.Add(price, bigOne) + } + + return price +} diff --git a/pkg/innerring/processors/settlement/basic/context.go b/pkg/innerring/processors/settlement/basic/context.go new file mode 100644 index 000000000..ac1ced8fe --- /dev/null +++ b/pkg/innerring/processors/settlement/basic/context.go @@ -0,0 +1,86 @@ +package basic + +import ( + "sync" + + "github.com/mr-tron/base58" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/common" + "github.com/nspcc-dev/neofs-node/pkg/morph/client/container/wrapper" + "go.uber.org/zap" +) + +type ( + EstimationFetcher interface { + Estimations(uint64) ([]*wrapper.Estimations, error) + } + + RateFetcher interface { + BasicRate() uint64 + } + + IncomeSettlementContext struct { + mu sync.Mutex // lock to prevent collection and distribution in the same time + + log *zap.Logger + epoch uint64 + + rate RateFetcher + estimations EstimationFetcher + container common.ContainerStorage + placement common.PlacementCalculator + exchange common.Exchanger + + txTable *common.TransferTable + bankOwner *owner.ID + } + + IncomeSettlementContextPrms struct { + Log *zap.Logger + Epoch uint64 + Rate RateFetcher + Estimations EstimationFetcher + Container common.ContainerStorage + Placement common.PlacementCalculator + Exchange common.Exchanger + } +) + +func NewIncomeSettlementContext(p *IncomeSettlementContextPrms) (*IncomeSettlementContext, error) { + bankingAccount, err := bankOwnerID() + if err != nil { + return nil, err // should never happen + } + + return &IncomeSettlementContext{ + log: p.Log, + epoch: p.Epoch, + rate: p.Rate, + estimations: p.Estimations, + container: p.Container, + placement: p.Placement, + exchange: p.Exchange, + txTable: common.NewTransferTable(), + bankOwner: bankingAccount, + }, nil +} + +func bankOwnerID() (*owner.ID, error) { + u := util.Uint160{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // todo: define const + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + + d, err := base58.Decode(address.Uint160ToString(u)) + if err != nil { + return nil, err + } + + var w owner.NEO3Wallet + copy(w[:], d) + + o := owner.NewID() + o.SetNeo3Wallet(&w) + + return o, nil +} diff --git a/pkg/innerring/processors/settlement/basic/distribute.go b/pkg/innerring/processors/settlement/basic/distribute.go new file mode 100644 index 000000000..3ec5e9797 --- /dev/null +++ b/pkg/innerring/processors/settlement/basic/distribute.go @@ -0,0 +1,6 @@ +package basic + +func (inc *IncomeSettlementContext) Distribute() { + inc.mu.Lock() + defer inc.mu.Unlock() +}