2021-01-26 10:27:30 +00:00
|
|
|
package audit
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-05-31 08:55:38 +00:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
2021-01-26 10:27:30 +00:00
|
|
|
"encoding/hex"
|
|
|
|
"math/big"
|
|
|
|
|
2021-05-31 08:55:38 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2021-02-01 12:40:07 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/common"
|
2021-01-26 10:27:30 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
|
2021-11-10 07:08:33 +00:00
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/audit"
|
|
|
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
2022-05-11 10:26:22 +00:00
|
|
|
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
2022-05-17 13:59:46 +00:00
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/user"
|
2021-01-26 10:27:30 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
2022-04-21 11:28:05 +00:00
|
|
|
// CalculatePrm groups the required parameters of
|
2021-01-26 10:27:30 +00:00
|
|
|
// Calculator.CalculateForEpoch call.
|
|
|
|
type CalculatePrm struct {
|
|
|
|
// Number of epoch to perform the calculation.
|
|
|
|
Epoch uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
type singleResultCtx struct {
|
|
|
|
eAudit uint64
|
|
|
|
|
|
|
|
auditResult *audit.Result
|
|
|
|
|
|
|
|
log *logger.Logger
|
|
|
|
|
2021-02-01 12:40:07 +00:00
|
|
|
txTable *common.TransferTable
|
2021-01-26 10:27:30 +00:00
|
|
|
|
2021-02-01 12:40:07 +00:00
|
|
|
cnrInfo common.ContainerInfo
|
2021-01-26 10:27:30 +00:00
|
|
|
|
2021-02-01 12:40:07 +00:00
|
|
|
cnrNodes []common.NodeInfo
|
2021-01-26 10:27:30 +00:00
|
|
|
|
2021-02-01 12:40:07 +00:00
|
|
|
passNodes map[string]common.NodeInfo
|
2021-01-26 10:27:30 +00:00
|
|
|
|
|
|
|
sumSGSize *big.Int
|
2021-04-07 10:51:47 +00:00
|
|
|
|
|
|
|
auditFee *big.Int
|
2021-01-26 10:27:30 +00:00
|
|
|
}
|
|
|
|
|
2021-01-28 17:05:52 +00:00
|
|
|
var (
|
|
|
|
bigGB = big.NewInt(1 << 30)
|
|
|
|
bigZero = big.NewInt(0)
|
|
|
|
bigOne = big.NewInt(1)
|
|
|
|
)
|
2021-01-26 10:27:30 +00:00
|
|
|
|
|
|
|
// Calculate calculates payments for audit results in a specific epoch of the network.
|
|
|
|
// Wraps the results in a money transfer transaction and sends it to the network.
|
|
|
|
func (c *Calculator) Calculate(p *CalculatePrm) {
|
|
|
|
log := c.opts.log.With(
|
|
|
|
zap.Uint64("current epoch", p.Epoch),
|
|
|
|
)
|
|
|
|
|
2021-04-07 10:51:47 +00:00
|
|
|
if p.Epoch == 0 {
|
|
|
|
log.Info("settlements are ignored for zero epoch")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-26 10:27:30 +00:00
|
|
|
log.Info("calculate audit settlements")
|
|
|
|
|
|
|
|
log.Debug("getting results for the previous epoch")
|
2021-04-07 12:12:36 +00:00
|
|
|
prevEpoch := p.Epoch - 1
|
2021-01-26 10:27:30 +00:00
|
|
|
|
2021-04-07 12:12:36 +00:00
|
|
|
auditResults, err := c.prm.ResultStorage.AuditResultsForEpoch(prevEpoch)
|
2021-01-26 10:27:30 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("could not collect audit results")
|
|
|
|
return
|
|
|
|
} else if len(auditResults) == 0 {
|
|
|
|
log.Debug("no audit results in previous epoch")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-07 10:51:47 +00:00
|
|
|
auditFee, err := c.prm.AuditFeeFetcher.AuditFee()
|
|
|
|
if err != nil {
|
|
|
|
log.Warn("can't fetch audit fee from network config",
|
|
|
|
zap.String("error", err.Error()))
|
|
|
|
auditFee = 0
|
|
|
|
}
|
|
|
|
|
2021-01-26 10:27:30 +00:00
|
|
|
log.Debug("processing audit results",
|
|
|
|
zap.Int("number", len(auditResults)),
|
|
|
|
)
|
|
|
|
|
2021-02-01 12:40:07 +00:00
|
|
|
table := common.NewTransferTable()
|
2021-01-26 10:27:30 +00:00
|
|
|
|
|
|
|
for i := range auditResults {
|
|
|
|
c.processResult(&singleResultCtx{
|
|
|
|
log: log,
|
|
|
|
auditResult: auditResults[i],
|
|
|
|
txTable: table,
|
2021-04-07 10:51:47 +00:00
|
|
|
auditFee: big.NewInt(0).SetUint64(auditFee),
|
2021-01-26 10:27:30 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debug("processing transfers")
|
|
|
|
|
2021-04-07 12:12:36 +00:00
|
|
|
common.TransferAssets(c.prm.Exchanger, table, common.AuditSettlementDetails(prevEpoch))
|
2021-01-26 10:27:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Calculator) processResult(ctx *singleResultCtx) {
|
|
|
|
ctx.log = ctx.log.With(
|
|
|
|
zap.Stringer("cid", ctx.containerID()),
|
2022-05-11 10:26:22 +00:00
|
|
|
zap.Uint64("audit epoch", ctx.auditResult.Epoch()),
|
2021-01-26 10:27:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
ctx.log.Debug("reading information about the container")
|
|
|
|
|
|
|
|
ok := c.readContainerInfo(ctx)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.log.Debug("building placement")
|
|
|
|
|
|
|
|
ok = c.buildPlacement(ctx)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.log.Debug("collecting passed nodes")
|
|
|
|
|
|
|
|
ok = c.collectPassNodes(ctx)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.log.Debug("calculating sum of the sizes of all storage groups")
|
|
|
|
|
|
|
|
ok = c.sumSGSizes(ctx)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.log.Debug("filling transfer table")
|
|
|
|
|
|
|
|
c.fillTransferTable(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Calculator) readContainerInfo(ctx *singleResultCtx) bool {
|
2022-05-12 16:37:46 +00:00
|
|
|
cnr, ok := ctx.auditResult.Container()
|
|
|
|
if !ok {
|
|
|
|
ctx.log.Error("missing container in audit result")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-01-26 10:27:30 +00:00
|
|
|
var err error
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
ctx.cnrInfo, err = c.prm.ContainerStorage.ContainerInfo(cnr)
|
2021-01-26 10:27:30 +00:00
|
|
|
if err != nil {
|
|
|
|
ctx.log.Error("could not get container info",
|
|
|
|
zap.String("error", err.Error()),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Calculator) buildPlacement(ctx *singleResultCtx) bool {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
ctx.cnrNodes, err = c.prm.PlacementCalculator.ContainerNodes(ctx.auditEpoch(), ctx.containerID())
|
|
|
|
if err != nil {
|
|
|
|
ctx.log.Error("could not get container nodes",
|
|
|
|
zap.String("error", err.Error()),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
empty := len(ctx.cnrNodes) == 0
|
|
|
|
if empty {
|
|
|
|
ctx.log.Debug("empty list of container nodes")
|
|
|
|
}
|
|
|
|
|
|
|
|
return err == nil && !empty
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Calculator) collectPassNodes(ctx *singleResultCtx) bool {
|
2021-02-01 12:40:07 +00:00
|
|
|
ctx.passNodes = make(map[string]common.NodeInfo)
|
2021-01-26 10:27:30 +00:00
|
|
|
|
|
|
|
for _, cnrNode := range ctx.cnrNodes {
|
2022-05-11 10:26:22 +00:00
|
|
|
// TODO(@cthulhu-rider): neofs-sdk-go#241 use dedicated method
|
|
|
|
ctx.auditResult.IteratePassedStorageNodes(func(passNode []byte) bool {
|
2021-01-26 10:27:30 +00:00
|
|
|
if !bytes.Equal(cnrNode.PublicKey(), passNode) {
|
2022-05-11 10:26:22 +00:00
|
|
|
return true
|
2021-01-26 10:27:30 +00:00
|
|
|
}
|
|
|
|
|
2022-05-11 10:26:22 +00:00
|
|
|
failed := false
|
|
|
|
|
|
|
|
ctx.auditResult.IterateFailedStorageNodes(func(failNode []byte) bool {
|
|
|
|
failed = bytes.Equal(cnrNode.PublicKey(), failNode)
|
|
|
|
return !failed
|
|
|
|
})
|
|
|
|
|
|
|
|
if !failed {
|
|
|
|
ctx.passNodes[hex.EncodeToString(passNode)] = cnrNode
|
2021-01-26 10:27:30 +00:00
|
|
|
}
|
|
|
|
|
2022-05-11 10:26:22 +00:00
|
|
|
return false
|
|
|
|
})
|
2021-01-26 10:27:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
empty := len(ctx.passNodes) == 0
|
|
|
|
if empty {
|
|
|
|
ctx.log.Debug("none of the container nodes passed the audit")
|
|
|
|
}
|
|
|
|
|
|
|
|
return !empty
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Calculator) sumSGSizes(ctx *singleResultCtx) bool {
|
|
|
|
sumPassSGSize := uint64(0)
|
2022-05-11 10:26:22 +00:00
|
|
|
fail := false
|
2021-01-26 10:27:30 +00:00
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
var addr oid.Address
|
|
|
|
addr.SetContainer(ctx.containerID())
|
2021-01-26 10:27:30 +00:00
|
|
|
|
2022-05-11 10:26:22 +00:00
|
|
|
ctx.auditResult.IteratePassedStorageGroups(func(id oid.ID) bool {
|
2022-05-31 17:00:41 +00:00
|
|
|
addr.SetObject(id)
|
2021-01-26 10:27:30 +00:00
|
|
|
|
|
|
|
sgInfo, err := c.prm.SGStorage.SGInfo(addr)
|
|
|
|
if err != nil {
|
|
|
|
ctx.log.Error("could not get SG info",
|
2022-05-11 10:26:22 +00:00
|
|
|
zap.String("id", id.String()),
|
|
|
|
zap.String("error", err.Error()),
|
2021-01-26 10:27:30 +00:00
|
|
|
)
|
|
|
|
|
2022-05-11 10:26:22 +00:00
|
|
|
fail = true
|
|
|
|
|
2021-01-26 10:27:30 +00:00
|
|
|
return false // we also can continue and calculate at least some part
|
|
|
|
}
|
|
|
|
|
|
|
|
sumPassSGSize += sgInfo.Size()
|
2022-05-11 10:26:22 +00:00
|
|
|
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
|
|
|
if fail {
|
|
|
|
return false
|
2021-01-26 10:27:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if sumPassSGSize == 0 {
|
|
|
|
ctx.log.Debug("zero sum SG size")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.sumSGSize = big.NewInt(int64(sumPassSGSize))
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Calculator) fillTransferTable(ctx *singleResultCtx) bool {
|
|
|
|
cnrOwner := ctx.cnrInfo.Owner()
|
|
|
|
|
2021-04-07 10:51:47 +00:00
|
|
|
// add txs to pay for storage node
|
2021-01-26 10:27:30 +00:00
|
|
|
for k, info := range ctx.passNodes {
|
|
|
|
ownerID, err := c.prm.AccountStorage.ResolveKey(info)
|
|
|
|
if err != nil {
|
|
|
|
ctx.log.Error("could not resolve public key of the storage node",
|
|
|
|
zap.String("error", err.Error()),
|
|
|
|
zap.String("key", k),
|
|
|
|
)
|
|
|
|
|
|
|
|
return false // we also can continue and calculate at least some part
|
|
|
|
}
|
|
|
|
|
|
|
|
price := info.Price()
|
|
|
|
|
|
|
|
ctx.log.Debug("calculating storage node salary for audit (GASe-12)",
|
|
|
|
zap.Stringer("sum SG size", ctx.sumSGSize),
|
|
|
|
zap.Stringer("price", price),
|
|
|
|
)
|
|
|
|
|
|
|
|
fee := big.NewInt(0).Mul(price, ctx.sumSGSize)
|
|
|
|
fee.Div(fee, bigGB)
|
|
|
|
|
2021-01-28 17:05:52 +00:00
|
|
|
if fee.Cmp(bigZero) == 0 {
|
|
|
|
fee.Add(fee, bigOne)
|
|
|
|
}
|
|
|
|
|
2021-02-01 12:40:07 +00:00
|
|
|
ctx.txTable.Transfer(&common.TransferTx{
|
|
|
|
From: cnrOwner,
|
2022-05-31 17:00:41 +00:00
|
|
|
To: *ownerID,
|
2021-02-01 12:40:07 +00:00
|
|
|
Amount: fee,
|
2021-01-26 10:27:30 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-04-07 10:51:47 +00:00
|
|
|
// add txs to pay inner ring node for audit result
|
2022-05-11 10:26:22 +00:00
|
|
|
auditIR, err := ownerFromKey(ctx.auditResult.AuditorKey())
|
2021-04-07 10:51:47 +00:00
|
|
|
if err != nil {
|
|
|
|
ctx.log.Error("could not parse public key of the inner ring node",
|
|
|
|
zap.String("error", err.Error()),
|
2022-05-11 10:26:22 +00:00
|
|
|
zap.String("key", hex.EncodeToString(ctx.auditResult.AuditorKey())),
|
2021-04-07 10:51:47 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.txTable.Transfer(&common.TransferTx{
|
|
|
|
From: cnrOwner,
|
2022-05-31 17:00:41 +00:00
|
|
|
To: *auditIR,
|
2021-04-07 10:51:47 +00:00
|
|
|
Amount: ctx.auditFee,
|
|
|
|
})
|
|
|
|
|
2021-01-26 10:27:30 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
func (c *singleResultCtx) containerID() cid.ID {
|
2022-05-12 16:37:46 +00:00
|
|
|
cnr, _ := c.auditResult.Container()
|
2022-05-31 17:00:41 +00:00
|
|
|
return cnr
|
2021-01-26 10:27:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *singleResultCtx) auditEpoch() uint64 {
|
|
|
|
if c.eAudit == 0 {
|
2022-05-11 10:26:22 +00:00
|
|
|
c.eAudit = c.auditResult.Epoch()
|
2021-01-26 10:27:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return c.eAudit
|
|
|
|
}
|
2021-04-07 10:51:47 +00:00
|
|
|
|
2022-05-17 13:59:46 +00:00
|
|
|
func ownerFromKey(key []byte) (*user.ID, error) {
|
2021-05-31 08:55:38 +00:00
|
|
|
pubKey, err := keys.NewPublicKeyFromBytes(key, elliptic.P256())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-04-07 10:51:47 +00:00
|
|
|
|
2022-05-17 13:59:46 +00:00
|
|
|
var id user.ID
|
|
|
|
user.IDFromKey(&id, (ecdsa.PublicKey)(*pubKey))
|
|
|
|
|
|
|
|
return &id, nil
|
2021-04-07 10:51:47 +00:00
|
|
|
}
|