+ +
++ NeoFS is a decentralized distributed object storage integrated with the NEO Blockchain. +
+ +--- +[![Report](https://goreportcard.com/badge/github.com/nspcc-dev/neo-go)](https://goreportcard.com/report/github.com/nspcc-dev/neofs-node) +![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nspcc-dev/neofs-node?sort=semver) +![License](https://img.shields.io/github/license/nspcc-dev/neofs-node.svg?style=popout) + +# Overview + +NeoFS Nodes are organized in peer-to-peer network that takes care of storing and +distributing user's data. Any Neo user may participate in the network and get +paid for providing storage resources to other users or store his data in NeoFS +and pay a competitive price for it. + +Users can reliably store object data in the NeoFS network and have a transparent +data placement process due to decentralized architecture and flexible storage +policies. Each node is responsible for executing the storage policies that the +users select for geographical location, reliability level, number of nodes, type +of disks, capacity, etc. Thus, NeoFS gives full control over data to users. + +Deep [Neo Blockchain](https://neo.org) integration allows NeoFS to be used by +dApp directly from +[NeoVM](https://docs.neo.org/docs/en-us/basic/technology/neovm.html) on the +[Smart Contract](https://docs.neo.org/docs/en-us/basic/technology/neocontract.html) +code level. This way dApps are not limited to on-chain storage and can +manipulate large amounts of data without paying a prohibitive price. + +NeoFS has native [gRPC](https://grpc.io) API and popular protocol gates such as +[AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html), +[HTTP](https://wikipedia.org/wiki/Hypertext_Transfer_Protocol), +[FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and +[sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing +developers to easily integrate applications without rewriting their code. + +# Contributing + +Feel free to contribute to this project after reading the [contributing +guidelines](CONTRIBUTING.md). + +Before starting to work on a certain topic, create an new issue first, +describing the feature/topic you are going to implement. + +# Credits + +NeoFS is maintained by [NeoSPCC](https://nspcc.ru) with the help and +contributions from community members. + +Please see [CREDITS](CREDITS.md) for details. + +# License + +- [GNU General Public License v3.0](LICENSE) diff --git a/cmd/neofs-ir/defaults.go b/cmd/neofs-ir/defaults.go new file mode 100644 index 000000000..508c003af --- /dev/null +++ b/cmd/neofs-ir/defaults.go @@ -0,0 +1,74 @@ +package main + +import ( + "strings" + + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neofs-node/misc" + "github.com/spf13/viper" +) + +func newConfig(path string) (*viper.Viper, error) { + var ( + err error + v = viper.New() + ) + + v.SetEnvPrefix(misc.InnerRingPrefix) + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + + v.SetDefault("app.name", misc.InnerRingName) + v.SetDefault("app.version", misc.Version) + + defaultConfiguration(v) + + if path != "" { + v.SetConfigFile(path) + v.SetConfigType("yml") // fixme: for now + err = v.ReadInConfig() + } + + return v, err +} + +func defaultConfiguration(cfg *viper.Viper) { + cfg.SetDefault("logger.level", "info") + cfg.SetDefault("logger.format", "console") + cfg.SetDefault("logger.trace_level", "fatal") + cfg.SetDefault("logger.no_disclaimer", false) + cfg.SetDefault("logger.sampling.initial", 1000) + cfg.SetDefault("logger.sampling.thereafter", 1000) + + cfg.SetDefault("pprof.enabled", false) + cfg.SetDefault("pprof.address", ":6060") + cfg.SetDefault("pprof.shutdown_ttl", "30s") + + cfg.SetDefault("metrics.enabled", false) + cfg.SetDefault("metrics.address", ":9090") + cfg.SetDefault("metrics.shutdown_ttl", "30s") + + cfg.SetDefault("morph.endpoint.client", "") + cfg.SetDefault("morph.endpoint.notification", "") + cfg.SetDefault("morph.dial_timeout", "10s") + cfg.SetDefault("morph.magic_number", uint32(netmode.PrivNet)) + + cfg.SetDefault("mainnet.endpoint.client", "") + cfg.SetDefault("mainnet.endpoint.notification", "") + cfg.SetDefault("mainnet.dial_timeout", "10s") + cfg.SetDefault("mainnet.magic_number", uint32(netmode.PrivNet)) + + cfg.SetDefault("key", "") // inner ring node key + + cfg.SetDefault("contracts.netmap", "") + cfg.SetDefault("contracts.neofs", "") + cfg.SetDefault("contracts.balance", "") + // gas native contract + cfg.SetDefault("contracts.gas", "8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b") + + cfg.SetDefault("timers.epoch", "5s") + + cfg.SetDefault("workers.netmap", "10") + cfg.SetDefault("workers.balance", "10") + cfg.SetDefault("workers.neofs", "10") +} diff --git a/cmd/neofs-ir/main.go b/cmd/neofs-ir/main.go new file mode 100644 index 000000000..2f634f5b2 --- /dev/null +++ b/cmd/neofs-ir/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/nspcc-dev/neofs-node/misc" + "github.com/nspcc-dev/neofs-node/pkg/innerring" + "github.com/nspcc-dev/neofs-node/pkg/util/grace" + "github.com/nspcc-dev/neofs-node/pkg/util/logger" + "github.com/nspcc-dev/neofs-node/pkg/util/profiler" +) + +const ( + // ErrorReturnCode returns when application crashed at initialization stage + ErrorReturnCode = 1 + + // SuccessReturnCode returns when application closed without panic + SuccessReturnCode = 0 +) + +func exitErr(err error) { + if err != nil { + fmt.Println(err) + os.Exit(ErrorReturnCode) + } +} + +func main() { + configFile := flag.String("config", "", "path to config") + versionFlag := flag.Bool("version", false, "neofs-ir node version") + flag.Parse() + + if *versionFlag { + fmt.Println("version:", misc.Version) + os.Exit(SuccessReturnCode) + } + + cfg, err := newConfig(*configFile) + exitErr(err) + + log, err := logger.NewLogger(cfg) + exitErr(err) + + ctx := grace.NewGracefulContext(log) + + pprof := profiler.NewProfiler(log, cfg) + prometheus := profiler.NewMetrics(log, cfg) + + innerRing, err := innerring.New(ctx, log, cfg) + if err != nil { + exitErr(err) + } + + // start pprof if enabled + if pprof != nil { + pprof.Start(ctx) + defer pprof.Stop() + } + + // start prometheus if enabled + if prometheus != nil { + prometheus.Start(ctx) + defer prometheus.Stop() + } + + // start inner ring + err = innerRing.Start(ctx) + if err != nil { + exitErr(err) + } + + log.Info("application started") + + // todo: select ctx.Done or exported error channel + <-ctx.Done() + + innerRing.Stop() + + log.Info("application stopped") +} diff --git a/cmd/neofs-node/defaults.go b/cmd/neofs-node/defaults.go index 4fe24b50e..dfe26eb52 100644 --- a/cmd/neofs-node/defaults.go +++ b/cmd/neofs-node/defaults.go @@ -4,8 +4,7 @@ import ( "time" "github.com/nspcc-dev/neo-go/pkg/config/netmode" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/modules/morph" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/morph" "github.com/spf13/viper" ) @@ -65,23 +64,6 @@ func setDefaults(v *viper.Viper) { }) } - // Storage section - { - storageTypes := []string{ - core.BlobStore.String(), - core.MetaStore.String(), - core.SpaceMetricsStore.String(), - } - - for i := range storageTypes { - v.SetDefault("storage."+storageTypes[i]+".bucket", "boltdb") - v.SetDefault("storage."+storageTypes[i]+".path", "./temp/storage/"+storageTypes[i]) - v.SetDefault("storage."+storageTypes[i]+".perm", 0777) - // v.SetDefault("storage."+storageTypes[i]+".no_grow_sync", false) - // v.SetDefault("storage."+storageTypes[i]+".lock_timeout", "30s") - } - } - // Object section { v.SetDefault("object.max_processing_size", 100) // size in MB, use 0 to remove restriction @@ -274,20 +256,6 @@ func setDefaults(v *viper.Viper) { ) } - { // Reputation - // Put method name - v.SetDefault( - morph.ReputationContractPutOptPath(), - "Put", - ) - - // List method name - v.SetDefault( - morph.ReputationContractListOptPath(), - "List", - ) - } - { // Netmap // AddPeer method name v.SetDefault( diff --git a/cmd/neofs-node/main.go b/cmd/neofs-node/main.go index 851f800c6..4e00169ad 100644 --- a/cmd/neofs-node/main.go +++ b/cmd/neofs-node/main.go @@ -12,14 +12,14 @@ import ( "github.com/nspcc-dev/neofs-api-go/service" state2 "github.com/nspcc-dev/neofs-api-go/state" crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/nspcc-dev/neofs-node/lib/fix" - "github.com/nspcc-dev/neofs-node/lib/fix/config" - "github.com/nspcc-dev/neofs-node/lib/fix/web" - "github.com/nspcc-dev/neofs-node/lib/fix/worker" - "github.com/nspcc-dev/neofs-node/lib/muxer" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/config" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/worker" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/node" "github.com/nspcc-dev/neofs-node/misc" - "github.com/nspcc-dev/neofs-node/modules/node" - "github.com/nspcc-dev/neofs-node/services/public/state" + "github.com/nspcc-dev/neofs-node/pkg/network/muxer" + statesrv "github.com/nspcc-dev/neofs-node/pkg/network/transport/state/grpc" + "github.com/nspcc-dev/neofs-node/pkg/util/profiler" "github.com/pkg/errors" "github.com/spf13/viper" "go.uber.org/dig" @@ -30,9 +30,9 @@ import ( type params struct { dig.In - Debug web.Profiler `optional:"true"` - Metric web.Metrics `optional:"true"` - Worker worker.Workers `optional:"true"` + Debug profiler.Profiler `optional:"true"` + Metric profiler.Metrics `optional:"true"` + Worker worker.Workers `optional:"true"` Muxer muxer.Mux Logger *zap.Logger } @@ -105,7 +105,7 @@ func runHealthCheck() { grpc.WithInsecure()) check(err) - req := new(state.HealthRequest) + req := new(statesrv.HealthRequest) req.SetTTL(service.NonForwardingTTL) if err := service.SignRequestData(key, req); err != nil { check(err) diff --git a/modules/bootstrap/healthy.go b/cmd/neofs-node/modules/bootstrap/healthy.go similarity index 69% rename from modules/bootstrap/healthy.go rename to cmd/neofs-node/modules/bootstrap/healthy.go index bd93fd0fd..116b424e7 100644 --- a/modules/bootstrap/healthy.go +++ b/cmd/neofs-node/modules/bootstrap/healthy.go @@ -2,12 +2,12 @@ package bootstrap import ( "crypto/ecdsa" + "errors" "sync" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/placement" - "github.com/nspcc-dev/neofs-node/services/public/state" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper" + state "github.com/nspcc-dev/neofs-node/pkg/network/transport/state/grpc" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" "github.com/spf13/viper" "go.uber.org/dig" "go.uber.org/zap" @@ -25,7 +25,7 @@ type ( // for ChangeState PrivateKey *ecdsa.PrivateKey - MorphNetmapContract *implementations.MorphNetmapContract + Client *contract.Wrapper } healthyResult struct { @@ -47,9 +47,7 @@ type ( } ) -const ( - errUnhealthy = internal.Error("unhealthy") -) +var errUnhealthy = errors.New("unhealthy") func (h *healthyClient) setHandler(handler func() error) { if handler == nil { @@ -71,12 +69,12 @@ func (h *healthyClient) Healthy() error { func newHealthy(p healthyParams) (res healthyResult, err error) { sp := state.Params{ - Stater: p.Place, - Logger: p.Logger, - Viper: p.Viper, - Checkers: p.Checkers, - PrivateKey: p.PrivateKey, - MorphNetmapContract: p.MorphNetmapContract, + Stater: p.Place, + Logger: p.Logger, + Viper: p.Viper, + Checkers: p.Checkers, + PrivateKey: p.PrivateKey, + Client: p.Client, } if res.StateService, err = state.New(sp); err != nil { diff --git a/modules/bootstrap/module.go b/cmd/neofs-node/modules/bootstrap/module.go similarity index 62% rename from modules/bootstrap/module.go rename to cmd/neofs-node/modules/bootstrap/module.go index 8b31ed2e8..e66552545 100644 --- a/modules/bootstrap/module.go +++ b/cmd/neofs-node/modules/bootstrap/module.go @@ -1,8 +1,6 @@ package bootstrap -import ( - "github.com/nspcc-dev/neofs-node/lib/fix/module" -) +import "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/module" // Module is a module of bootstrap component. var Module = module.Module{ diff --git a/lib/fix/catch.go b/cmd/neofs-node/modules/fix/catch.go similarity index 100% rename from lib/fix/catch.go rename to cmd/neofs-node/modules/fix/catch.go diff --git a/lib/fix/config/config.go b/cmd/neofs-node/modules/fix/config/config.go similarity index 100% rename from lib/fix/config/config.go rename to cmd/neofs-node/modules/fix/config/config.go diff --git a/lib/fix/fix.go b/cmd/neofs-node/modules/fix/fix.go similarity index 85% rename from lib/fix/fix.go rename to cmd/neofs-node/modules/fix/fix.go index 7fd4e9df3..d3990a267 100644 --- a/lib/fix/fix.go +++ b/cmd/neofs-node/modules/fix/fix.go @@ -6,10 +6,11 @@ import ( "strconv" "strings" - "github.com/nspcc-dev/neofs-node/lib/fix/config" - "github.com/nspcc-dev/neofs-node/lib/fix/logger" - "github.com/nspcc-dev/neofs-node/lib/fix/module" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/config" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/module" "github.com/nspcc-dev/neofs-node/misc" + "github.com/nspcc-dev/neofs-node/pkg/util/grace" + "github.com/nspcc-dev/neofs-node/pkg/util/logger" "github.com/pkg/errors" "github.com/spf13/viper" "go.uber.org/dig" @@ -90,7 +91,7 @@ func New(s *Settings, mod module.Module) App { mod = mod.Append( module.Module{ {Constructor: logger.NewLogger}, - {Constructor: NewGracefulContext}, + {Constructor: grace.NewGracefulContext}, {Constructor: func() (*viper.Viper, error) { return config.NewConfig(config.Params{ File: s.File, diff --git a/lib/fix/module/module.go b/cmd/neofs-node/modules/fix/module/module.go similarity index 100% rename from lib/fix/module/module.go rename to cmd/neofs-node/modules/fix/module/module.go diff --git a/lib/fix/services.go b/cmd/neofs-node/modules/fix/services.go similarity index 100% rename from lib/fix/services.go rename to cmd/neofs-node/modules/fix/services.go diff --git a/lib/fix/worker/worker.go b/cmd/neofs-node/modules/fix/worker/worker.go similarity index 100% rename from lib/fix/worker/worker.go rename to cmd/neofs-node/modules/fix/worker/worker.go diff --git a/modules/grpc/billing.go b/cmd/neofs-node/modules/grpc/billing.go similarity index 100% rename from modules/grpc/billing.go rename to cmd/neofs-node/modules/grpc/billing.go diff --git a/modules/grpc/module.go b/cmd/neofs-node/modules/grpc/module.go similarity index 58% rename from modules/grpc/module.go rename to cmd/neofs-node/modules/grpc/module.go index 7e2660391..861084ad4 100644 --- a/modules/grpc/module.go +++ b/cmd/neofs-node/modules/grpc/module.go @@ -1,8 +1,6 @@ package grpc -import ( - "github.com/nspcc-dev/neofs-node/lib/fix/module" -) +import "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/module" // Module is a gRPC layer module. var Module = module.Module{ diff --git a/modules/grpc/routing.go b/cmd/neofs-node/modules/grpc/routing.go similarity index 96% rename from modules/grpc/routing.go rename to cmd/neofs-node/modules/grpc/routing.go index d0fc6fca6..bd97e42c5 100644 --- a/modules/grpc/routing.go +++ b/cmd/neofs-node/modules/grpc/routing.go @@ -8,6 +8,7 @@ import ( middleware "github.com/grpc-ecosystem/go-grpc-middleware" gZap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + libgrpc "github.com/nspcc-dev/neofs-node/pkg/network/transport/grpc" "github.com/spf13/viper" "go.uber.org/dig" "go.uber.org/zap" @@ -15,11 +16,7 @@ import ( ) type ( - // Service interface - Service interface { - Name() string - Register(*grpc.Server) - } + Service = libgrpc.Service // ServerParams to create gRPC-server // and provide service-handlers diff --git a/modules/morph/balance.go b/cmd/neofs-node/modules/morph/balance.go similarity index 61% rename from modules/morph/balance.go rename to cmd/neofs-node/modules/morph/balance.go index df3964421..e22200021 100644 --- a/modules/morph/balance.go +++ b/cmd/neofs-node/modules/morph/balance.go @@ -1,8 +1,9 @@ package morph import ( - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/services/public/accounting" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/balance" + clientWrapper "github.com/nspcc-dev/neofs-node/pkg/morph/client/balance/wrapper" + accounting "github.com/nspcc-dev/neofs-node/pkg/network/transport/accounting/grpc" "github.com/pkg/errors" "go.uber.org/dig" ) @@ -10,7 +11,7 @@ import ( type balanceContractResult struct { dig.Out - BalanceContract implementations.MorphBalanceContract + Client *clientWrapper.Wrapper AccountingService accounting.Service } @@ -41,27 +42,28 @@ func newBalanceContract(p contractParams) (res balanceContractResult, err error) return } - morphClient := implementations.MorphBalanceContract{} - morphClient.SetBalanceContractClient(client) + var ( + balanceOfMethod = p.Viper.GetString(BalanceContractBalanceOfOptPath()) + decimalsMethod = p.Viper.GetString(BalanceContractDecimalsOfOptPath()) + ) - morphClient.SetBalanceOfMethodName( - p.Viper.GetString( - BalanceContractBalanceOfOptPath(), - ), - ) - morphClient.SetDecimalsMethodName( - p.Viper.GetString( - BalanceContractDecimalsOfOptPath(), - ), - ) + var c *contract.Client + if c, err = contract.New(client, + contract.WithBalanceOfMethod(balanceOfMethod), + contract.WithDecimalsMethod(decimalsMethod), + ); err != nil { + return + } + + if res.Client, err = clientWrapper.New(c); err != nil { + return + } if res.AccountingService, err = accounting.New(accounting.Params{ - MorphBalanceContract: morphClient, + ContractClient: res.Client, }); err != nil { return } - res.BalanceContract = morphClient - return } diff --git a/modules/morph/common.go b/cmd/neofs-node/modules/morph/common.go similarity index 83% rename from modules/morph/common.go rename to cmd/neofs-node/modules/morph/common.go index 6584f7ae6..55fa64c44 100644 --- a/modules/morph/common.go +++ b/cmd/neofs-node/modules/morph/common.go @@ -2,17 +2,16 @@ package morph import ( "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neofs-api-go/bootstrap" - "github.com/nspcc-dev/neofs-node/lib/blockchain/event" - "github.com/nspcc-dev/neofs-node/lib/blockchain/goclient" - "github.com/nspcc-dev/neofs-node/lib/implementations" + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" "github.com/spf13/viper" "go.uber.org/dig" "go.uber.org/zap" ) // SmartContracts maps smart contract name to contract client. -type SmartContracts map[string]implementations.StaticContractClient +type SmartContracts map[string]*client.StaticClient // EventHandlers maps notification event name to handler information. type EventHandlers map[string]event.HandlerInfo @@ -22,7 +21,7 @@ type morphContractsParams struct { Viper *viper.Viper - GoClient *goclient.Client + Client *client.Client Listener event.Listener } @@ -36,11 +35,11 @@ type contractParams struct { MorphContracts SmartContracts - NodeInfo bootstrap.NodeInfo + NodeInfo netmap.Info } func newMorphContracts(p morphContractsParams) (SmartContracts, EventHandlers, error) { - mContracts := make(map[string]implementations.StaticContractClient, len(ContractNames)) + mContracts := make(map[string]*client.StaticClient, len(ContractNames)) mHandlers := make(map[string]event.HandlerInfo) for _, contractName := range ContractNames { @@ -59,7 +58,7 @@ func newMorphContracts(p morphContractsParams) (SmartContracts, EventHandlers, e ), ) - mContracts[contractName], err = implementations.NewStaticContractClient(p.GoClient, scHash, fee) + mContracts[contractName], err = client.NewStatic(p.Client, scHash, fee) if err != nil { return nil, nil, err } @@ -109,7 +108,6 @@ const ( // ContractNames is a list of smart contract names. var ContractNames = []string{ containerContractName, - reputationContractName, NetmapContractName, BalanceContractName, } diff --git a/modules/morph/container.go b/cmd/neofs-node/modules/morph/container.go similarity index 61% rename from modules/morph/container.go rename to cmd/neofs-node/modules/morph/container.go index 770bf4b74..550d35c84 100644 --- a/modules/morph/container.go +++ b/cmd/neofs-node/modules/morph/container.go @@ -1,9 +1,10 @@ package morph import ( - "github.com/nspcc-dev/neofs-node/lib/acl" - "github.com/nspcc-dev/neofs-node/lib/container" - "github.com/nspcc-dev/neofs-node/lib/implementations" + eacl "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" + clientWrapper "github.com/nspcc-dev/neofs-node/pkg/morph/client/container/wrapper" "github.com/pkg/errors" "go.uber.org/dig" ) @@ -11,13 +12,9 @@ import ( type containerContractResult struct { dig.Out - ContainerContract *implementations.MorphContainerContract + ExtendedACLStore eacl.Storage - BinaryExtendedACLStore acl.BinaryExtendedACLStore - - ExtendedACLSource acl.ExtendedACLSource - - ContainerStorage container.Storage + ContainerStorage storage.Storage } const ( @@ -73,50 +70,34 @@ func newContainerContract(p contractParams) (res containerContractResult, err er return } - morphClient := new(implementations.MorphContainerContract) - morphClient.SetContainerContractClient(client) - - morphClient.SetEACLSetMethodName( - p.Viper.GetString( - ContainerContractSetEACLOptPath(), - ), - ) - morphClient.SetEACLGetMethodName( - p.Viper.GetString( - ContainerContractEACLOptPath(), - ), - ) - morphClient.SetContainerGetMethodName( - p.Viper.GetString( - ContainerContractGetOptPath(), - ), - ) - morphClient.SetContainerPutMethodName( - p.Viper.GetString( - ContainerContractPutOptPath(), - ), - ) - morphClient.SetContainerDeleteMethodName( - p.Viper.GetString( - ContainerContractDelOptPath(), - ), - ) - morphClient.SetContainerListMethodName( - p.Viper.GetString( - ContainerContractListOptPath(), - ), + var ( + setEACLMethod = p.Viper.GetString(ContainerContractSetEACLOptPath()) + eaclMethod = p.Viper.GetString(ContainerContractEACLOptPath()) + getMethod = p.Viper.GetString(ContainerContractGetOptPath()) + putMethod = p.Viper.GetString(ContainerContractPutOptPath()) + deleteMethod = p.Viper.GetString(ContainerContractDelOptPath()) + listMethod = p.Viper.GetString(ContainerContractListOptPath()) ) - res.ContainerContract = morphClient - - res.BinaryExtendedACLStore = morphClient - - res.ExtendedACLSource, err = implementations.ExtendedACLSourceFromBinary(res.BinaryExtendedACLStore) - if err != nil { + var containerClient *contract.Client + if containerClient, err = contract.New(client, + contract.WithSetEACLMethod(setEACLMethod), + contract.WithEACLMethod(eaclMethod), + contract.WithGetMethod(getMethod), + contract.WithPutMethod(putMethod), + contract.WithDeleteMethod(deleteMethod), + contract.WithListMethod(listMethod), + ); err != nil { return } - res.ContainerStorage = morphClient + var wrapClient *clientWrapper.Wrapper + if wrapClient, err = clientWrapper.New(containerClient); err != nil { + return + } + + res.ContainerStorage = wrapClient + res.ExtendedACLStore = wrapClient return res, nil } diff --git a/modules/morph/event.go b/cmd/neofs-node/modules/morph/event.go similarity index 82% rename from modules/morph/event.go rename to cmd/neofs-node/modules/morph/event.go index 4df3f486c..b6d6a631d 100644 --- a/modules/morph/event.go +++ b/cmd/neofs-node/modules/morph/event.go @@ -1,8 +1,8 @@ package morph import ( - "github.com/nspcc-dev/neofs-node/lib/blockchain/event" - "github.com/nspcc-dev/neofs-node/lib/blockchain/event/netmap" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/nspcc-dev/neofs-node/pkg/morph/event/netmap" ) const eventOpt = "event" diff --git a/cmd/neofs-node/modules/morph/goclient.go b/cmd/neofs-node/modules/morph/goclient.go new file mode 100644 index 000000000..77b521496 --- /dev/null +++ b/cmd/neofs-node/modules/morph/goclient.go @@ -0,0 +1,31 @@ +package morph + +import ( + "crypto/ecdsa" + + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/spf13/viper" + "go.uber.org/dig" + "go.uber.org/zap" +) + +type morphClientParams struct { + dig.In + + Viper *viper.Viper + + Logger *zap.Logger + + Key *ecdsa.PrivateKey +} + +func newClient(p morphClientParams) (*client.Client, error) { + return client.New( + p.Key, + p.Viper.GetString(optPath(prefix, endpointOpt)), + client.WithLogger(p.Logger), + client.WithDialTimeout(p.Viper.GetDuration(optPath(prefix, dialTimeoutOpt))), + client.WithMagic(netmode.Magic(p.Viper.GetUint32(optPath(prefix, magicNumberOpt)))), + ) +} diff --git a/modules/morph/listener.go b/cmd/neofs-node/modules/morph/listener.go similarity index 90% rename from modules/morph/listener.go rename to cmd/neofs-node/modules/morph/listener.go index 4c334ced9..d70154432 100644 --- a/modules/morph/listener.go +++ b/cmd/neofs-node/modules/morph/listener.go @@ -3,8 +3,8 @@ package morph import ( "context" - "github.com/nspcc-dev/neofs-node/lib/blockchain/event" - "github.com/nspcc-dev/neofs-node/lib/blockchain/subscriber" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/nspcc-dev/neofs-node/pkg/morph/subscriber" "github.com/spf13/viper" "go.uber.org/dig" "go.uber.org/zap" diff --git a/modules/morph/module.go b/cmd/neofs-node/modules/morph/module.go similarity index 74% rename from modules/morph/module.go rename to cmd/neofs-node/modules/morph/module.go index c2ae26378..b61ce2c73 100644 --- a/modules/morph/module.go +++ b/cmd/neofs-node/modules/morph/module.go @@ -3,15 +3,14 @@ package morph import ( "strings" - "github.com/nspcc-dev/neofs-node/lib/fix/module" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/module" ) // Module is a Neo:Morph module. var Module = module.Module{ - {Constructor: newMorphClient}, + {Constructor: newClient}, {Constructor: newMorphContracts}, {Constructor: newContainerContract}, - {Constructor: newReputationContract}, {Constructor: newNetmapContract}, {Constructor: newEventListener}, {Constructor: newBalanceContract}, diff --git a/modules/morph/netmap.go b/cmd/neofs-node/modules/morph/netmap.go similarity index 58% rename from modules/morph/netmap.go rename to cmd/neofs-node/modules/morph/netmap.go index 3c5e4f66a..1fb8452f5 100644 --- a/modules/morph/netmap.go +++ b/cmd/neofs-node/modules/morph/netmap.go @@ -1,10 +1,9 @@ package morph import ( - "github.com/nspcc-dev/neofs-node/lib/boot" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/ir" - "github.com/nspcc-dev/neofs-node/lib/netmap" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" + clientWrapper "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper" + "github.com/nspcc-dev/neofs-node/pkg/network/bootstrap" "github.com/pkg/errors" "go.uber.org/dig" ) @@ -12,13 +11,9 @@ import ( type netmapContractResult struct { dig.Out - NetmapContract *implementations.MorphNetmapContract + Client *clientWrapper.Wrapper - NetMapStorage netmap.Storage - - IRStorage ir.Storage - - StorageBootController boot.StorageBootController + NodeRegisterer *bootstrap.Registerer } const ( @@ -68,48 +63,32 @@ func newNetmapContract(p contractParams) (res netmapContractResult, err error) { return } - morphClient := new(implementations.MorphNetmapContract) - morphClient.SetNetmapContractClient(client) - - morphClient.SetAddPeerMethodName( - p.Viper.GetString( - NetmapContractAddPeerOptPath(), - ), - ) - morphClient.SetNewEpochMethodName( - p.Viper.GetString( - NetmapContractNewEpochOptPath(), - ), - ) - morphClient.SetNetMapMethodName( - p.Viper.GetString( - NetmapContractNetmapOptPath(), - ), - ) - morphClient.SetUpdateStateMethodName( - p.Viper.GetString( - NetmapContractUpdateStateOptPath(), - ), - ) - morphClient.SetIRListMethodName( - p.Viper.GetString( - NetmapContractIRListOptPath(), - ), + var ( + addPeerMethod = p.Viper.GetString(NetmapContractAddPeerOptPath()) + newEpochMethod = p.Viper.GetString(NetmapContractNewEpochOptPath()) + netmapMethod = p.Viper.GetString(NetmapContractNetmapOptPath()) + updStateMethod = p.Viper.GetString(NetmapContractUpdateStateOptPath()) + irListMethod = p.Viper.GetString(NetmapContractIRListOptPath()) ) - bootCtrl := boot.StorageBootController{} - bootCtrl.SetPeerBootstrapper(morphClient) - bootCtrl.SetLogger(p.Logger) + var c *contract.Client + if c, err = contract.New(client, + contract.WithAddPeerMethod(addPeerMethod), + contract.WithNewEpochMethod(newEpochMethod), + contract.WithNetMapMethod(netmapMethod), + contract.WithUpdateStateMethod(updStateMethod), + contract.WithInnerRingListMethod(irListMethod), + ); err != nil { + return + } - bootPrm := boot.StorageBootParams{} - bootPrm.SetNodeInfo(&p.NodeInfo) + if res.Client, err = clientWrapper.New(c); err != nil { + return + } - bootCtrl.SetBootParams(bootPrm) - - res.StorageBootController = bootCtrl - res.NetmapContract = morphClient - res.NetMapStorage = morphClient - res.IRStorage = morphClient + if res.NodeRegisterer, err = bootstrap.New(res.Client, p.NodeInfo); err != nil { + return + } return res, nil } diff --git a/modules/network/http.go b/cmd/neofs-node/modules/network/http.go similarity index 92% rename from modules/network/http.go rename to cmd/neofs-node/modules/network/http.go index 21fbd7226..001411c47 100644 --- a/modules/network/http.go +++ b/cmd/neofs-node/modules/network/http.go @@ -2,7 +2,7 @@ package network import ( "github.com/fasthttp/router" - svc "github.com/nspcc-dev/neofs-node/modules/bootstrap" + svc "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/bootstrap" "github.com/valyala/fasthttp" "go.uber.org/dig" ) diff --git a/modules/network/module.go b/cmd/neofs-node/modules/network/module.go similarity index 58% rename from modules/network/module.go rename to cmd/neofs-node/modules/network/module.go index 95c6041f3..8e0e1f253 100644 --- a/modules/network/module.go +++ b/cmd/neofs-node/modules/network/module.go @@ -1,8 +1,8 @@ package network import ( - "github.com/nspcc-dev/neofs-node/lib/fix/module" - "github.com/nspcc-dev/neofs-node/lib/fix/web" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/module" + "github.com/nspcc-dev/neofs-node/pkg/util/profiler" ) // Module is a network layer module. @@ -10,11 +10,10 @@ var Module = module.Module{ {Constructor: newMuxer}, {Constructor: newPeers}, {Constructor: newPlacement}, - {Constructor: newTransport}, // Metrics is prometheus handler - {Constructor: web.NewMetrics}, + {Constructor: profiler.NewMetrics}, // Profiler is pprof handler - {Constructor: web.NewProfiler}, + {Constructor: profiler.NewProfiler}, {Constructor: newHTTPHandler}, } diff --git a/modules/network/muxer.go b/cmd/neofs-node/modules/network/muxer.go similarity index 89% rename from modules/network/muxer.go rename to cmd/neofs-node/modules/network/muxer.go index 63ad8fc5b..2de81f25a 100644 --- a/modules/network/muxer.go +++ b/cmd/neofs-node/modules/network/muxer.go @@ -4,8 +4,7 @@ import ( "time" "github.com/multiformats/go-multiaddr" - "github.com/nspcc-dev/neofs-node/lib/muxer" - "github.com/nspcc-dev/neofs-node/lib/peers" + "github.com/nspcc-dev/neofs-node/pkg/network/muxer" "github.com/spf13/viper" "github.com/valyala/fasthttp" "go.uber.org/dig" @@ -19,8 +18,6 @@ type muxerParams struct { Logger *zap.Logger P2P *grpc.Server - Peers peers.Interface - Address multiaddr.Multiaddr ShutdownTTL time.Duration `name:"shutdown_ttl"` API fasthttp.RequestHandler @@ -48,7 +45,6 @@ func newFastHTTPServer(p muxerParams) *fasthttp.Server { func newMuxer(p muxerParams) muxer.Mux { return muxer.New(muxer.Params{ P2P: p.P2P, - Peers: p.Peers, Logger: p.Logger, Address: p.Address, ShutdownTTL: p.ShutdownTTL, diff --git a/modules/network/peers.go b/cmd/neofs-node/modules/network/peers.go similarity index 52% rename from modules/network/peers.go rename to cmd/neofs-node/modules/network/peers.go index f9af19c0a..3fe60b77b 100644 --- a/modules/network/peers.go +++ b/cmd/neofs-node/modules/network/peers.go @@ -2,8 +2,7 @@ package network import ( "github.com/multiformats/go-multiaddr" - "github.com/nspcc-dev/neofs-node/lib/peers" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/network/peers" "github.com/spf13/viper" "go.uber.org/dig" "go.uber.org/zap" @@ -12,26 +11,14 @@ import ( type peersParams struct { dig.In - Viper *viper.Viper - Logger *zap.Logger - Address multiaddr.Multiaddr - Transport transport.Transport -} - -func newTransport(v *viper.Viper) transport.Transport { - return transport.New( - v.GetInt64("transport.attempts_count"), - v.GetDuration("transport.attempts_ttl"), - ) + Viper *viper.Viper + Logger *zap.Logger + Address multiaddr.Multiaddr } func newPeers(p peersParams) (peers.Interface, error) { return peers.New(peers.Params{ Logger: p.Logger, - Address: p.Address, - Transport: p.Transport, - Attempts: p.Viper.GetInt64("peers.attempts_count"), - AttemptsTTL: p.Viper.GetDuration("peers.attempts_ttl"), ConnectionTTL: p.Viper.GetDuration("peers.connections_ttl"), ConnectionIDLE: p.Viper.GetDuration("peers.connections_idle"), MetricsTimeout: p.Viper.GetDuration("peers.metrics_timeout"), diff --git a/modules/network/placement.go b/cmd/neofs-node/modules/network/placement.go similarity index 67% rename from modules/network/placement.go rename to cmd/neofs-node/modules/network/placement.go index 36959efdf..4b1d8bd80 100644 --- a/modules/network/placement.go +++ b/cmd/neofs-node/modules/network/placement.go @@ -1,14 +1,14 @@ package network import ( - "github.com/nspcc-dev/neofs-node/lib/blockchain/event" - netmapevent "github.com/nspcc-dev/neofs-node/lib/blockchain/event/netmap" - libcnr "github.com/nspcc-dev/neofs-node/lib/container" - "github.com/nspcc-dev/neofs-node/lib/netmap" - "github.com/nspcc-dev/neofs-node/lib/peers" - "github.com/nspcc-dev/neofs-node/lib/placement" - "github.com/nspcc-dev/neofs-node/modules/morph" - "github.com/nspcc-dev/neofs-node/services/public/state" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/morph" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + netmapevent "github.com/nspcc-dev/neofs-node/pkg/morph/event/netmap" + "github.com/nspcc-dev/neofs-node/pkg/network/peers" + state "github.com/nspcc-dev/neofs-node/pkg/network/transport/state/grpc" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" "go.uber.org/dig" "go.uber.org/zap" ) @@ -19,11 +19,11 @@ type ( Log *zap.Logger Peers peers.Store - Fetcher libcnr.Storage + Fetcher storage.Storage MorphEventListener event.Listener - NetMapStorage netmap.Storage + NetMapClient *contract.Wrapper MorphEventHandlers morph.EventHandlers } @@ -51,7 +51,7 @@ func newPlacement(p placementParams) placementOutput { morph.NewEpochEventType, )]; ok { handlerInfo.SetHandler(func(ev event.Event) { - nmRes, err := p.NetMapStorage.GetNetMap(netmap.GetParams{}) + nm, err := p.NetMapClient.GetNetMap() if err != nil { p.Log.Error("could not get network map", zap.String("error", err.Error()), @@ -61,7 +61,7 @@ func newPlacement(p placementParams) placementOutput { if err := place.Update( ev.(netmapevent.NewEpoch).EpochNumber(), - nmRes.NetMap(), + nm, ); err != nil { p.Log.Error("could not update network map in placement component", zap.String("error", err.Error()), diff --git a/modules/node/audit.go b/cmd/neofs-node/modules/node/audit.go similarity index 64% rename from modules/node/audit.go rename to cmd/neofs-node/modules/node/audit.go index a2c02b288..77f22420c 100644 --- a/modules/node/audit.go +++ b/cmd/neofs-node/modules/node/audit.go @@ -4,9 +4,11 @@ import ( "crypto/ecdsa" "github.com/nspcc-dev/neofs-api-go/session" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/peers" - "github.com/nspcc-dev/neofs-node/services/public/object" + "github.com/nspcc-dev/neofs-node/pkg/network/peers" + object "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/replication/storage" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" "github.com/spf13/viper" "go.uber.org/zap" ) @@ -15,7 +17,7 @@ type ( cnrHandlerParams struct { *viper.Viper *zap.Logger - Placer implementations.ObjectPlacer + Placer *placement.PlacementWrapper PeerStore peers.Store Peers peers.Interface TimeoutsPrefix string @@ -25,8 +27,8 @@ type ( } ) -func newObjectsContainerHandler(p cnrHandlerParams) (implementations.SelectiveContainerExecutor, error) { - as, err := implementations.NewAddressStore(p.PeerStore, p.Logger) +func newObjectsContainerHandler(p cnrHandlerParams) (transport.SelectiveContainerExecutor, error) { + as, err := storage.NewAddressStore(p.PeerStore, p.Logger) if err != nil { return nil, err } @@ -50,12 +52,12 @@ func newObjectsContainerHandler(p cnrHandlerParams) (implementations.SelectiveCo return nil, err } - exec, err := implementations.NewContainerTraverseExecutor(multiTransport) + exec, err := transport.NewContainerTraverseExecutor(multiTransport) if err != nil { return nil, err } - return implementations.NewObjectContainerHandler(implementations.ObjectContainerHandlerParams{ + return transport.NewObjectContainerHandler(transport.ObjectContainerHandlerParams{ NodeLister: p.Placer, Executor: exec, Logger: p.Logger, diff --git a/modules/node/container.go b/cmd/neofs-node/modules/node/container.go similarity index 52% rename from modules/node/container.go rename to cmd/neofs-node/modules/node/container.go index af081cb4c..a0cac8ab7 100644 --- a/modules/node/container.go +++ b/cmd/neofs-node/modules/node/container.go @@ -1,10 +1,10 @@ package node import ( - "github.com/nspcc-dev/neofs-node/lib/acl" - libcnr "github.com/nspcc-dev/neofs-node/lib/container" - svc "github.com/nspcc-dev/neofs-node/modules/bootstrap" - "github.com/nspcc-dev/neofs-node/services/public/container" + svc "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/bootstrap" + eacl "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + container "github.com/nspcc-dev/neofs-node/pkg/network/transport/container/grpc" "go.uber.org/dig" "go.uber.org/zap" ) @@ -16,9 +16,9 @@ type cnrParams struct { Healthy svc.HealthyClient - ExtendedACLStore acl.BinaryExtendedACLStore + ExtendedACLStore eacl.Storage - ContainerStorage libcnr.Storage + ContainerStorage storage.Storage } func newContainerService(p cnrParams) (container.Service, error) { diff --git a/cmd/neofs-node/modules/node/core.go b/cmd/neofs-node/modules/node/core.go new file mode 100644 index 000000000..cde0172ba --- /dev/null +++ b/cmd/neofs-node/modules/node/core.go @@ -0,0 +1,35 @@ +package node + +import ( + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket/boltdb" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket/fsbucket" + "github.com/spf13/viper" +) + +type Buckets map[string]bucket.Bucket + +const ( + fsBucket = "fsbucket" + boltBucket = "bolt" +) + +func newBuckets(v *viper.Viper) (Buckets, error) { + var ( + err error + mBuckets = make(Buckets) + ) + + if mBuckets[fsBucket], err = fsbucket.NewBucket(v); err != nil { + return nil, err + } + + boltOpts, err := boltdb.NewOptions(v) + if err != nil { + return nil, err + } else if mBuckets[boltBucket], err = boltdb.NewBucket(&boltOpts); err != nil { + return nil, err + } + + return mBuckets, nil +} diff --git a/modules/node/localstore.go b/cmd/neofs-node/modules/node/localstore.go similarity index 55% rename from modules/node/localstore.go rename to cmd/neofs-node/modules/node/localstore.go index 7be10bed0..c96eb1f48 100644 --- a/modules/node/localstore.go +++ b/cmd/neofs-node/modules/node/localstore.go @@ -1,10 +1,9 @@ package node import ( - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/meta" - "github.com/nspcc-dev/neofs-node/lib/metrics" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + meta2 "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/meta" + metrics2 "github.com/nspcc-dev/neofs-node/pkg/services/metrics" "go.uber.org/atomic" "go.uber.org/dig" "go.uber.org/zap" @@ -15,9 +14,9 @@ type ( dig.In Logger *zap.Logger - Storage core.Storage + Buckets Buckets Counter *atomic.Float64 - Collector metrics.Collector + Collector metrics2.Collector } metaIterator struct { @@ -25,30 +24,20 @@ type ( } ) -func newMetaIterator(iter localstore.Iterator) meta.Iterator { +func newMetaIterator(iter localstore.Iterator) meta2.Iterator { return &metaIterator{iter: iter} } -func (m *metaIterator) Iterate(handler meta.IterateFunc) error { +func (m *metaIterator) Iterate(handler meta2.IterateFunc) error { return m.iter.Iterate(nil, func(objMeta *localstore.ObjectMeta) bool { return handler == nil || handler(objMeta.Object) != nil }) } func newLocalstore(p localstoreParams) (localstore.Localstore, error) { - metaBucket, err := p.Storage.GetBucket(core.MetaStore) - if err != nil { - return nil, err - } - - blobBucket, err := p.Storage.GetBucket(core.BlobStore) - if err != nil { - return nil, err - } - local, err := localstore.New(localstore.Params{ - BlobBucket: blobBucket, - MetaBucket: metaBucket, + BlobBucket: p.Buckets[fsBucket], + MetaBucket: p.Buckets[boltBucket], Logger: p.Logger, Collector: p.Collector, }) diff --git a/modules/node/metrics.go b/cmd/neofs-node/modules/node/metrics.go similarity index 54% rename from modules/node/metrics.go rename to cmd/neofs-node/modules/node/metrics.go index 0faad5d1c..1eba1d69d 100644 --- a/modules/node/metrics.go +++ b/cmd/neofs-node/modules/node/metrics.go @@ -1,9 +1,8 @@ package node import ( - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/metrics" - mService "github.com/nspcc-dev/neofs-node/services/metrics" + metrics "github.com/nspcc-dev/neofs-node/pkg/network/transport/metrics/grpc" + metrics2 "github.com/nspcc-dev/neofs-node/pkg/services/metrics" "github.com/spf13/viper" "go.uber.org/atomic" "go.uber.org/dig" @@ -17,36 +16,31 @@ type ( Logger *zap.Logger Options []string `name:"node_options"` Viper *viper.Viper - Store core.Storage + Buckets Buckets } metricsServiceParams struct { dig.In Logger *zap.Logger - Collector metrics.Collector + Collector metrics2.Collector } ) func newObjectCounter() *atomic.Float64 { return atomic.NewFloat64(0) } -func newMetricsService(p metricsServiceParams) (mService.Service, error) { - return mService.New(mService.Params{ +func newMetricsService(p metricsServiceParams) (metrics.Service, error) { + return metrics.New(metrics.Params{ Logger: p.Logger, Collector: p.Collector, }) } -func newMetricsCollector(p metricsParams) (metrics.Collector, error) { - store, err := p.Store.GetBucket(core.SpaceMetricsStore) - if err != nil { - return nil, err - } - - return metrics.New(metrics.Params{ +func newMetricsCollector(p metricsParams) (metrics2.Collector, error) { + return metrics2.New(metrics2.Params{ Options: p.Options, Logger: p.Logger, Interval: p.Viper.GetDuration("metrics_collector.interval"), - MetricsStore: store, + MetricsStore: p.Buckets[fsBucket], }) } diff --git a/modules/node/module.go b/cmd/neofs-node/modules/node/module.go similarity index 59% rename from modules/node/module.go rename to cmd/neofs-node/modules/node/module.go index 83a81b484..6edc42d7e 100644 --- a/modules/node/module.go +++ b/cmd/neofs-node/modules/node/module.go @@ -2,20 +2,19 @@ package node import ( "github.com/nspcc-dev/neofs-api-go/session" - "github.com/nspcc-dev/neofs-node/lib/blockchain/event" - "github.com/nspcc-dev/neofs-node/lib/boot" - "github.com/nspcc-dev/neofs-node/lib/fix/module" - "github.com/nspcc-dev/neofs-node/lib/fix/worker" - "github.com/nspcc-dev/neofs-node/lib/metrics" - "github.com/nspcc-dev/neofs-node/lib/netmap" - "github.com/nspcc-dev/neofs-node/lib/peers" - "github.com/nspcc-dev/neofs-node/lib/replication" - "github.com/nspcc-dev/neofs-node/modules/bootstrap" - "github.com/nspcc-dev/neofs-node/modules/grpc" - "github.com/nspcc-dev/neofs-node/modules/morph" - "github.com/nspcc-dev/neofs-node/modules/network" - "github.com/nspcc-dev/neofs-node/modules/settings" - "github.com/nspcc-dev/neofs-node/modules/workers" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/bootstrap" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/module" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/worker" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/grpc" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/morph" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/network" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/settings" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/workers" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + libboot "github.com/nspcc-dev/neofs-node/pkg/network/bootstrap" + "github.com/nspcc-dev/neofs-node/pkg/network/peers" + metrics2 "github.com/nspcc-dev/neofs-node/pkg/services/metrics" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/replication" "github.com/spf13/viper" "go.uber.org/dig" "go.uber.org/zap" @@ -30,11 +29,11 @@ type jobParams struct { Replicator replication.Manager PeersInterface peers.Interface - Metrics metrics.Collector + Metrics metrics2.Collector MorphEventListener event.Listener - StorageBootController boot.StorageBootController + NodeRegisterer *libboot.Registerer } // Module is a NeoFS node module. @@ -42,8 +41,7 @@ var Module = module.Module{ {Constructor: attachJobs}, {Constructor: newPeerstore}, {Constructor: attachServices}, - {Constructor: netmap.NewNetmap}, - {Constructor: newStorage}, + {Constructor: newBuckets}, {Constructor: newMetricsCollector}, {Constructor: newObjectCounter}, @@ -86,6 +84,6 @@ func attachJobs(p jobParams) worker.Jobs { "metrics": p.Metrics.Start, "event_listener": p.MorphEventListener.Listen, "replicator": p.Replicator.Process, - "boot": p.StorageBootController.Bootstrap, + "boot": p.NodeRegisterer.Bootstrap, } } diff --git a/modules/node/objectmanager.go b/cmd/neofs-node/modules/node/objectmanager.go similarity index 64% rename from modules/node/objectmanager.go rename to cmd/neofs-node/modules/node/objectmanager.go index 6d96f5c71..f54dd4fe6 100644 --- a/modules/node/objectmanager.go +++ b/cmd/neofs-node/modules/node/objectmanager.go @@ -7,16 +7,17 @@ import ( "github.com/nspcc-dev/neofs-api-go/hash" apiobj "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/session" - libacl "github.com/nspcc-dev/neofs-node/lib/acl" - "github.com/nspcc-dev/neofs-node/lib/container" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/ir" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/peers" - "github.com/nspcc-dev/neofs-node/lib/placement" - "github.com/nspcc-dev/neofs-node/lib/transformer" - "github.com/nspcc-dev/neofs-node/services/public/object" + eacl "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper" + "github.com/nspcc-dev/neofs-node/pkg/network/peers" + object "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" + storage2 "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/replication/storage" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport/storagegroup" "github.com/spf13/viper" "go.uber.org/dig" "go.uber.org/zap" @@ -38,22 +39,18 @@ type ( Options []string `name:"node_options"` Key *ecdsa.PrivateKey - IRStorage ir.Storage + NetMapClient *contract.Wrapper - EpochReceiver implementations.EpochReceiver + Placer *placement.PlacementWrapper - Placer implementations.ObjectPlacer + ExtendedACLStore eacl.Storage - ExtendedACLStore libacl.ExtendedACLSource - - ContainerStorage container.Storage + ContainerStorage storage.Storage } ) const ( transformersSectionPath = "object.transformers." - - aclMandatorySetBits = 0x04040444 ) const xorSalitor = "xor" @@ -65,7 +62,7 @@ func newObjectManager(p objectManagerParams) (object.Service, error) { sltr = hash.SaltXOR } - as, err := implementations.NewAddressStore(p.Peers, p.Logger) + as, err := storage2.NewAddressStore(p.Peers, p.Logger) if err != nil { return nil, err } @@ -81,7 +78,7 @@ func newObjectManager(p objectManagerParams) (object.Service, error) { tr, err := object.NewMultiTransport(object.MultiTransportParams{ AddressStore: as, - EpochReceiver: p.EpochReceiver, + EpochReceiver: p.Placer, RemoteService: rs, Logger: p.Logger, Key: p.Key, @@ -98,12 +95,12 @@ func newObjectManager(p objectManagerParams) (object.Service, error) { return nil, err } - exec, err := implementations.NewContainerTraverseExecutor(tr) + exec, err := transport.NewContainerTraverseExecutor(tr) if err != nil { return nil, err } - selectiveExec, err := implementations.NewObjectContainerHandler(implementations.ObjectContainerHandlerParams{ + selectiveExec, err := transport.NewObjectContainerHandler(transport.ObjectContainerHandlerParams{ NodeLister: p.Placer, Executor: exec, Logger: p.Logger, @@ -112,7 +109,7 @@ func newObjectManager(p objectManagerParams) (object.Service, error) { return nil, err } - sgInfoRecv, err := implementations.NewStorageGroupInfoReceiver(implementations.StorageGroupInfoReceiverParams{ + sgInfoRecv, err := storagegroup.NewStorageGroupInfoReceiver(storagegroup.StorageGroupInfoReceiverParams{ SelectiveContainerExecutor: selectiveExec, Logger: p.Logger, }) @@ -120,16 +117,14 @@ func newObjectManager(p objectManagerParams) (object.Service, error) { return nil, err } - verifier, err := implementations.NewLocalIntegrityVerifier( - core.NewNeoKeyVerifier(), - ) + verifier, err := storage2.NewLocalIntegrityVerifier() if err != nil { return nil, err } trans, err := transformer.NewTransformer(transformer.Params{ SGInfoReceiver: sgInfoRecv, - EpochReceiver: p.EpochReceiver, + EpochReceiver: p.Placer, SizeLimit: uint64(p.Viper.GetInt64(transformersSectionPath+"payload_limiter.max_payload_size") * apiobj.UnitsKB), Verifier: verifier, }) @@ -137,16 +132,7 @@ func newObjectManager(p objectManagerParams) (object.Service, error) { return nil, err } - aclChecker := libacl.NewMaskedBasicACLChecker(aclMandatorySetBits, libacl.DefaultAndFilter) - - aclHelper, err := implementations.NewACLHelper(p.ContainerStorage) - if err != nil { - return nil, err - } - - verifier, err = implementations.NewLocalHeadIntegrityVerifier( - core.NewNeoKeyVerifier(), - ) + verifier, err = storage2.NewLocalHeadIntegrityVerifier() if err != nil { return nil, err } @@ -163,16 +149,16 @@ func newObjectManager(p objectManagerParams) (object.Service, error) { ObjectRestorer: transformer.NewRestorePipeline( transformer.SplitRestorer(), ), - RemoteService: rs, - AddressStore: as, - Logger: p.Logger, - TokenStore: p.TokenStore, - EpochReceiver: p.EpochReceiver, - ContainerNodesLister: p.Placer, - Key: p.Key, - CheckACL: p.Viper.GetBool("object.check_acl"), - DialTimeout: p.Viper.GetDuration("object.dial_timeout"), - MaxPayloadSize: p.Viper.GetUint64("object.transformers.payload_limiter.max_payload_size") * uint64(apiobj.UnitsKB), + RemoteService: rs, + AddressStore: as, + Logger: p.Logger, + TokenStore: p.TokenStore, + EpochReceiver: p.Placer, + PlacementWrapper: p.Placer, + Key: p.Key, + CheckACL: p.Viper.GetBool("object.check_acl"), + DialTimeout: p.Viper.GetDuration("object.dial_timeout"), + MaxPayloadSize: p.Viper.GetUint64("object.transformers.payload_limiter.max_payload_size") * uint64(apiobj.UnitsKB), PutParams: object.OperationParams{ Timeout: pto, LogErrors: p.Viper.GetBool("object.put.log_errs"), @@ -205,15 +191,11 @@ func newObjectManager(p objectManagerParams) (object.Service, error) { WindowSize: p.Viper.GetInt("object.window_size"), - ACLHelper: aclHelper, - BasicACLChecker: aclChecker, - IRStorage: p.IRStorage, - ContainerLister: p.Placer, + ContainerStorage: p.ContainerStorage, + NetmapClient: p.NetMapClient, SGInfoReceiver: sgInfoRecv, - OwnerKeyVerifier: core.NewNeoKeyVerifier(), - ExtendedACLSource: p.ExtendedACLStore, }) } diff --git a/modules/node/peerstore.go b/cmd/neofs-node/modules/node/peerstore.go similarity index 90% rename from modules/node/peerstore.go rename to cmd/neofs-node/modules/node/peerstore.go index 1ccd1f1d6..05e68fc90 100644 --- a/modules/node/peerstore.go +++ b/cmd/neofs-node/modules/node/peerstore.go @@ -4,7 +4,7 @@ import ( "crypto/ecdsa" "github.com/multiformats/go-multiaddr" - "github.com/nspcc-dev/neofs-node/lib/peers" + "github.com/nspcc-dev/neofs-node/pkg/network/peers" "go.uber.org/dig" "go.uber.org/zap" ) diff --git a/cmd/neofs-node/modules/node/placement.go b/cmd/neofs-node/modules/node/placement.go new file mode 100644 index 000000000..015b6402f --- /dev/null +++ b/cmd/neofs-node/modules/node/placement.go @@ -0,0 +1,28 @@ +package node + +import ( + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" + "go.uber.org/dig" +) + +type ( + placementToolParams struct { + dig.In + + Placement placement.Component + } + + placementToolResult struct { + dig.Out + + Placer *placement.PlacementWrapper + } +) + +func newPlacementTool(p placementToolParams) (res placementToolResult, err error) { + if res.Placer, err = placement.NewObjectPlacer(p.Placement); err != nil { + return + } + + return +} diff --git a/modules/node/replication.go b/cmd/neofs-node/modules/node/replication.go similarity index 85% rename from modules/node/replication.go rename to cmd/neofs-node/modules/node/replication.go index 546fdda9b..0c0538124 100644 --- a/modules/node/replication.go +++ b/cmd/neofs-node/modules/node/replication.go @@ -6,16 +6,14 @@ import ( "github.com/nspcc-dev/neofs-api-go/hash" "github.com/nspcc-dev/neofs-api-go/session" - "github.com/nspcc-dev/neofs-node/lib/blockchain/event" - "github.com/nspcc-dev/neofs-node/lib/blockchain/event/netmap" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/ir" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/peers" - "github.com/nspcc-dev/neofs-node/lib/placement" - "github.com/nspcc-dev/neofs-node/lib/replication" - "github.com/nspcc-dev/neofs-node/modules/morph" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/morph" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/nspcc-dev/neofs-node/pkg/morph/event/netmap" + "github.com/nspcc-dev/neofs-node/pkg/network/peers" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/replication" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/replication/storage" "github.com/pkg/errors" "github.com/spf13/viper" "go.uber.org/dig" @@ -34,10 +32,9 @@ type ( Peers peers.Store Placement placement.Component Logger *zap.Logger - Lister ir.Storage Key *ecdsa.PrivateKey - Placer implementations.ObjectPlacer + Placer *placement.PlacementWrapper TokenStore session.PrivateTokenStore @@ -57,7 +54,7 @@ const ( ) func newReplicationManager(p replicationManagerParams) (replication.Manager, error) { - as, err := implementations.NewAddressStore(p.Peers, p.Logger) + as, err := storage.NewAddressStore(p.Peers, p.Logger) if err != nil { return nil, err } @@ -80,14 +77,12 @@ func newReplicationManager(p replicationManagerParams) (replication.Manager, err return nil, err } - integrityVerifier, err := implementations.NewLocalIntegrityVerifier( - core.NewNeoKeyVerifier(), - ) + integrityVerifier, err := storage.NewLocalIntegrityVerifier() if err != nil { return nil, err } - verifier, err := implementations.NewObjectValidator(&implementations.ObjectValidatorParams{ + verifier, err := storage.NewObjectValidator(&storage.ObjectValidatorParams{ AddressStore: ms, Localstore: p.LocalStore, Logger: p.Logger, @@ -183,7 +178,7 @@ func newPlacementHonorer(p replicationManagerParams, rss replication.RemoteStora return nil, err } - storage, err := implementations.NewObjectStorage(implementations.ObjectStorageParams{ + storage, err := storage.NewObjectStorage(storage.ObjectStorageParams{ Localstore: p.LocalStore, SelectiveContainerExecutor: och, Logger: p.Logger, @@ -221,7 +216,7 @@ func newLocationDetector(p replicationManagerParams, ms replication.MultiSolver) return nil, err } - locator, err := implementations.NewObjectLocator(implementations.LocatorParams{ + locator, err := storage.NewObjectLocator(storage.LocatorParams{ SelectiveContainerExecutor: och, Logger: p.Logger, }) @@ -243,7 +238,7 @@ func newLocationDetector(p replicationManagerParams, ms replication.MultiSolver) func newStorageValidator(p replicationManagerParams, as replication.AddressStore) (replication.StorageValidator, error) { prefix := mainReplicationPrefix + "." + storageValidatorPrefix - var sltr implementations.Salitor + var sltr storage.Salitor switch v := p.Viper.GetString(prefix + ".salitor"); v { case xorSalitor: @@ -267,14 +262,12 @@ func newStorageValidator(p replicationManagerParams, as replication.AddressStore return nil, err } - headVerifier, err := implementations.NewLocalHeadIntegrityVerifier( - core.NewNeoKeyVerifier(), - ) + headVerifier, err := storage.NewLocalHeadIntegrityVerifier() if err != nil { return nil, err } - verifier, err := implementations.NewObjectValidator(&implementations.ObjectValidatorParams{ + verifier, err := storage.NewObjectValidator(&storage.ObjectValidatorParams{ AddressStore: as, Localstore: p.LocalStore, SelectiveContainerExecutor: och, @@ -317,7 +310,7 @@ func newObjectReplicator(p replicationManagerParams, rss replication.RemoteStora return nil, err } - storage, err := implementations.NewObjectStorage(implementations.ObjectStorageParams{ + storage, err := storage.NewObjectStorage(storage.ObjectStorageParams{ Localstore: p.LocalStore, SelectiveContainerExecutor: och, Logger: p.Logger, @@ -355,14 +348,12 @@ func newRestorer(p replicationManagerParams, ms replication.MultiSolver) (replic return nil, err } - integrityVerifier, err := implementations.NewLocalIntegrityVerifier( - core.NewNeoKeyVerifier(), - ) + integrityVerifier, err := storage.NewLocalIntegrityVerifier() if err != nil { return nil, err } - verifier, err := implementations.NewObjectValidator(&implementations.ObjectValidatorParams{ + verifier, err := storage.NewObjectValidator(&storage.ObjectValidatorParams{ AddressStore: ms, Localstore: p.LocalStore, SelectiveContainerExecutor: och, @@ -373,7 +364,7 @@ func newRestorer(p replicationManagerParams, ms replication.MultiSolver) (replic return nil, err } - storage, err := implementations.NewObjectStorage(implementations.ObjectStorageParams{ + storage, err := storage.NewObjectStorage(storage.ObjectStorageParams{ Localstore: p.LocalStore, Logger: p.Logger, }) diff --git a/cmd/neofs-node/modules/node/services.go b/cmd/neofs-node/modules/node/services.go new file mode 100644 index 000000000..3aec901ed --- /dev/null +++ b/cmd/neofs-node/modules/node/services.go @@ -0,0 +1,36 @@ +package node + +import ( + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/grpc" + accounting "github.com/nspcc-dev/neofs-node/pkg/network/transport/accounting/grpc" + container "github.com/nspcc-dev/neofs-node/pkg/network/transport/container/grpc" + metrics "github.com/nspcc-dev/neofs-node/pkg/network/transport/metrics/grpc" + object "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc" + session "github.com/nspcc-dev/neofs-node/pkg/network/transport/session/grpc" + state "github.com/nspcc-dev/neofs-node/pkg/network/transport/state/grpc" + "go.uber.org/dig" +) + +type servicesParams struct { + dig.In + + Status state.Service + Container container.Service + Object object.Service + Session session.Service + Accounting accounting.Service + Metrics metrics.Service +} + +func attachServices(p servicesParams) grpc.ServicesResult { + return grpc.ServicesResult{ + Services: []grpc.Service{ + p.Status, + p.Container, + p.Accounting, + p.Metrics, + p.Session, + p.Object, + }, + } +} diff --git a/modules/node/session.go b/cmd/neofs-node/modules/node/session.go similarity index 65% rename from modules/node/session.go rename to cmd/neofs-node/modules/node/session.go index aaa252779..46aaed8b7 100644 --- a/modules/node/session.go +++ b/cmd/neofs-node/modules/node/session.go @@ -1,8 +1,8 @@ package node import ( - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/services/public/session" + session "github.com/nspcc-dev/neofs-node/pkg/network/transport/session/grpc" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" "go.uber.org/dig" "go.uber.org/zap" ) @@ -14,7 +14,7 @@ type sessionParams struct { TokenStore session.TokenStore - EpochReceiver implementations.EpochReceiver + EpochReceiver *placement.PlacementWrapper } func newSessionService(p sessionParams) (session.Service, error) { diff --git a/modules/settings/address.go b/cmd/neofs-node/modules/settings/address.go similarity index 85% rename from modules/settings/address.go rename to cmd/neofs-node/modules/settings/address.go index c1c9722c4..53cc80349 100644 --- a/modules/settings/address.go +++ b/cmd/neofs-node/modules/settings/address.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/multiformats/go-multiaddr" - "github.com/nspcc-dev/neofs-node/internal" "github.com/pkg/errors" ) @@ -21,10 +20,10 @@ const emptyAddr = "0.0.0.0" const ip4ColonCount = 1 var ( - errEmptyAddress = internal.Error("`node.address` could not be empty") - errEmptyProtocol = internal.Error("`node.protocol` could not be empty") - errUnknownProtocol = internal.Error("`node.protocol` unknown protocol") - errEmptyShutdownTTL = internal.Error("`node.shutdown_ttl` could not be empty") + errEmptyAddress = errors.New("`node.address` could not be empty") + errEmptyProtocol = errors.New("`node.protocol` could not be empty") + errUnknownProtocol = errors.New("`node.protocol` unknown protocol") + errEmptyShutdownTTL = errors.New("`node.shutdown_ttl` could not be empty") ) func ipVersion(address string) string { diff --git a/modules/settings/module.go b/cmd/neofs-node/modules/settings/module.go similarity index 61% rename from modules/settings/module.go rename to cmd/neofs-node/modules/settings/module.go index 1e075103d..6980967e3 100644 --- a/modules/settings/module.go +++ b/cmd/neofs-node/modules/settings/module.go @@ -1,8 +1,6 @@ package settings -import ( - "github.com/nspcc-dev/neofs-node/lib/fix/module" -) +import "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/module" // Module is a node settings module. var Module = module.Module{ diff --git a/modules/settings/node.go b/cmd/neofs-node/modules/settings/node.go similarity index 90% rename from modules/settings/node.go rename to cmd/neofs-node/modules/settings/node.go index 47b940e69..1cdf2a593 100644 --- a/modules/settings/node.go +++ b/cmd/neofs-node/modules/settings/node.go @@ -10,9 +10,9 @@ import ( "time" "github.com/multiformats/go-multiaddr" - "github.com/nspcc-dev/neofs-api-go/bootstrap" crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/nspcc-dev/neofs-node/lib/peers" + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" + "github.com/nspcc-dev/neofs-node/pkg/network/peers" "github.com/pkg/errors" "github.com/spf13/viper" "go.uber.org/dig" @@ -28,7 +28,7 @@ type ( NodeOpts []string `name:"node_options"` ShutdownTTL time.Duration `name:"shutdown_ttl"` - NodeInfo bootstrap.NodeInfo + NodeInfo netmap.Info } ) @@ -136,11 +136,12 @@ loop: cfg.NodeOpts = append(cfg.NodeOpts, val) } - cfg.NodeInfo = bootstrap.NodeInfo{ - Address: cfg.Address.String(), - PubKey: crypto.MarshalPublicKey(&cfg.PrivateKey.PublicKey), - Options: cfg.NodeOpts, - } + nodeInfo := netmap.Info{} + nodeInfo.SetAddress(cfg.Address.String()) + nodeInfo.SetPublicKey(crypto.MarshalPublicKey(&cfg.PrivateKey.PublicKey)) + nodeInfo.SetOptions(cfg.NodeOpts) + + cfg.NodeInfo = nodeInfo l.Debug("loaded node options", zap.Strings("options", cfg.NodeOpts)) diff --git a/modules/workers/module.go b/cmd/neofs-node/modules/workers/module.go similarity index 58% rename from modules/workers/module.go rename to cmd/neofs-node/modules/workers/module.go index 275a5faf2..a95da32b3 100644 --- a/modules/workers/module.go +++ b/cmd/neofs-node/modules/workers/module.go @@ -1,8 +1,6 @@ package workers -import ( - "github.com/nspcc-dev/neofs-node/lib/fix/module" -) +import "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/module" // Module is a workers module. var Module = module.Module{ diff --git a/modules/workers/prepare.go b/cmd/neofs-node/modules/workers/prepare.go similarity index 96% rename from modules/workers/prepare.go rename to cmd/neofs-node/modules/workers/prepare.go index ea5411fbf..45930b10c 100644 --- a/modules/workers/prepare.go +++ b/cmd/neofs-node/modules/workers/prepare.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/nspcc-dev/neofs-node/lib/fix/worker" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/modules/fix/worker" "github.com/spf13/viper" "go.uber.org/dig" "go.uber.org/zap" diff --git a/go.mod b/go.mod index be64ab5ed..1c1628ae3 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,6 @@ go 1.14 require ( bou.ke/monkey v1.0.2 - github.com/cenk/backoff v2.2.1+incompatible // indirect - github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect github.com/fasthttp/router v1.0.2 github.com/gogo/protobuf v1.3.1 github.com/golang/protobuf v1.4.2 @@ -17,15 +15,13 @@ require ( github.com/multiformats/go-multiaddr-net v0.1.2 // v0.1.1 => v0.1.2 github.com/multiformats/go-multihash v0.0.13 github.com/nspcc-dev/hrw v1.0.9 - github.com/nspcc-dev/neo-go v0.90.0-pre.0.20200708064050-cf1e5243b90b - github.com/nspcc-dev/neofs-api-go v1.2.0 + github.com/nspcc-dev/neo-go v0.90.0 + github.com/nspcc-dev/neofs-api-go v1.3.0 github.com/nspcc-dev/neofs-crypto v0.3.0 github.com/nspcc-dev/netmap v1.7.0 github.com/panjf2000/ants/v2 v2.3.0 - github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.6.0 - github.com/rubyist/circuitbreaker v2.2.1+incompatible github.com/soheilhy/cmux v0.1.4 github.com/spaolacci/murmur3 v1.1.0 github.com/spf13/pflag v1.0.5 // indirect diff --git a/go.sum b/go.sum index a397a2427..4b62e23e1 100644 Binary files a/go.sum and b/go.sum differ diff --git a/internal/error.go b/internal/error.go deleted file mode 100644 index 7df160339..000000000 --- a/internal/error.go +++ /dev/null @@ -1,7 +0,0 @@ -package internal - -// Error is a custom error. -type Error string - -// Error is an implementation of error interface. -func (e Error) Error() string { return string(e) } diff --git a/lib/.gitkeep b/lib/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/acl/action.go b/lib/acl/action.go deleted file mode 100644 index a173d92e9..000000000 --- a/lib/acl/action.go +++ /dev/null @@ -1,94 +0,0 @@ -package acl - -import ( - "bytes" - - "github.com/nspcc-dev/neofs-api-go/acl" -) - -// RequestInfo is an interface of request information needed for extended ACL check. -type RequestInfo interface { - TypedHeaderSource - - // Must return the binary representation of request initiator's key. - Key() []byte - - // Must return true if request corresponds to operation type. - TypeOf(acl.OperationType) bool - - // Must return true if request has passed target. - TargetOf(acl.Target) bool -} - -// ExtendedACLChecker is an interface of extended ACL checking tool. -type ExtendedACLChecker interface { - // Must return an action according to the results of applying the ACL table rules to request. - // - // Must return ActionUndefined if it is unable to explicitly calculate the action. - Action(acl.ExtendedACLTable, RequestInfo) acl.ExtendedACLAction -} - -type extendedACLChecker struct{} - -// NewExtendedACLChecker creates a new extended ACL checking tool and returns ExtendedACLChecker interface. -func NewExtendedACLChecker() ExtendedACLChecker { - return new(extendedACLChecker) -} - -// Action returns an action for passed request based on information about it and ACL table. -// -// Returns action of the first suitable table record, or ActionUndefined in the absence thereof. -// -// If passed ExtendedACLTable is nil, ActionUndefined returns. -// If passed RequestInfo is nil, ActionUndefined returns. -func (s extendedACLChecker) Action(table acl.ExtendedACLTable, req RequestInfo) acl.ExtendedACLAction { - if table == nil { - return acl.ActionUndefined - } else if req == nil { - return acl.ActionUndefined - } - - for _, record := range table.Records() { - // check type of operation - if !req.TypeOf(record.OperationType()) { - continue - } - - // check target - if !targetMatches(req, record.TargetList()) { - continue - } - - // check headers - switch MatchFilters(req, record.HeaderFilters()) { - case mResUndefined: - // headers of some type could not be composed => allow - return acl.ActionAllow - case mResMatch: - return record.Action() - } - } - - return acl.ActionAllow -} - -// returns true if one of ExtendedACLTarget has suitable target OR suitable public key. -func targetMatches(req RequestInfo, list []acl.ExtendedACLTarget) bool { - rKey := req.Key() - - for _, target := range list { - // check public key match - for _, key := range target.KeyList() { - if bytes.Equal(key, rKey) { - return true - } - } - - // check target group match - if req.TargetOf(target.Target()) { - return true - } - } - - return false -} diff --git a/lib/acl/action_test.go b/lib/acl/action_test.go deleted file mode 100644 index 49e30eea8..000000000 --- a/lib/acl/action_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package acl - -import ( - "testing" - - "github.com/nspcc-dev/neofs-api-go/acl" - "github.com/stretchr/testify/require" -) - -type testExtendedACLTable struct { - records []acl.ExtendedACLRecord -} - -type testRequestInfo struct { - headers []acl.TypedHeader - key []byte - opType acl.OperationType - target acl.Target -} - -type testEACLRecord struct { - opType acl.OperationType - filters []acl.HeaderFilter - targets []acl.ExtendedACLTarget - action acl.ExtendedACLAction -} - -type testEACLTarget struct { - target acl.Target - keys [][]byte -} - -func (s testEACLTarget) Target() acl.Target { - return s.target -} - -func (s testEACLTarget) KeyList() [][]byte { - return s.keys -} - -func (s testEACLRecord) OperationType() acl.OperationType { - return s.opType -} - -func (s testEACLRecord) HeaderFilters() []acl.HeaderFilter { - return s.filters -} - -func (s testEACLRecord) TargetList() []acl.ExtendedACLTarget { - return s.targets -} - -func (s testEACLRecord) Action() acl.ExtendedACLAction { - return s.action -} - -func (s testRequestInfo) HeadersOfType(typ acl.HeaderType) ([]acl.Header, bool) { - res := make([]acl.Header, 0, len(s.headers)) - - for i := range s.headers { - if s.headers[i].HeaderType() == typ { - res = append(res, s.headers[i]) - } - } - - return res, true -} - -func (s testRequestInfo) Key() []byte { - return s.key -} - -func (s testRequestInfo) TypeOf(t acl.OperationType) bool { - return s.opType == t -} - -func (s testRequestInfo) TargetOf(t acl.Target) bool { - return s.target == t -} - -func (s testExtendedACLTable) Records() []acl.ExtendedACLRecord { - return s.records -} - -func TestExtendedACLChecker_Action(t *testing.T) { - s := NewExtendedACLChecker() - - // nil ExtendedACLTable - require.Equal(t, acl.ActionUndefined, s.Action(nil, nil)) - - // create test ExtendedACLTable - table := new(testExtendedACLTable) - - // nil RequestInfo - require.Equal(t, acl.ActionUndefined, s.Action(table, nil)) - - // create test RequestInfo - req := new(testRequestInfo) - - // create test ExtendedACLRecord - record := new(testEACLRecord) - table.records = append(table.records, record) - - // set different OperationType - record.opType = acl.OperationType(3) - req.opType = record.opType + 1 - - require.Equal(t, acl.ActionAllow, s.Action(table, req)) - - // set equal OperationType - req.opType = record.opType - - // create test ExtendedACLTarget through group - target := new(testEACLTarget) - record.targets = append(record.targets, target) - - // set not matching ExtendedACLTarget - target.target = acl.Target(5) - req.target = target.target + 1 - - require.Equal(t, acl.ActionAllow, s.Action(table, req)) - - // set matching ExtendedACLTarget - req.target = target.target - - // create test HeaderFilter - fHeader := new(testTypedHeader) - hFilter := &testHeaderFilter{ - TypedHeader: fHeader, - } - record.filters = append(record.filters, hFilter) - - // create test TypedHeader - header := new(testTypedHeader) - req.headers = append(req.headers, header) - - // set not matching values - header.t = hFilter.HeaderType() + 1 - - require.Equal(t, acl.ActionAllow, s.Action(table, req)) - - // set matching values - header.k = "key" - header.v = "value" - - fHeader.t = header.HeaderType() - fHeader.k = header.Name() - fHeader.v = header.Value() - - hFilter.t = acl.StringEqual - - // set ExtendedACLAction - record.action = acl.ExtendedACLAction(7) - - require.Equal(t, record.action, s.Action(table, req)) - - // set matching ExtendedACLTarget through key - target.target = req.target + 1 - req.key = []byte{1, 2, 3} - target.keys = append(target.keys, req.key) - - require.Equal(t, record.action, s.Action(table, req)) -} diff --git a/lib/acl/basic.go b/lib/acl/basic.go deleted file mode 100644 index eae2d7fa9..000000000 --- a/lib/acl/basic.go +++ /dev/null @@ -1,179 +0,0 @@ -package acl - -import ( - "github.com/nspcc-dev/neofs-api-go/acl" - "github.com/nspcc-dev/neofs-api-go/object" - "github.com/nspcc-dev/neofs-node/internal" -) - -type ( - // BasicChecker is an interface of the basic ACL control tool. - BasicChecker interface { - // Action returns true if request is allowed for this target. - Action(uint32, object.RequestType, acl.Target) (bool, error) - - // Bearer returns true if bearer token is allowed for this request. - Bearer(uint32, object.RequestType) (bool, error) - - // Extended returns true if extended ACL is allowed for this. - Extended(uint32) bool - - // Sticky returns true if sticky bit is set. - Sticky(uint32) bool - } - - // BasicACLChecker performs basic ACL check. - BasicACLChecker struct{} - - // MaskedBasicACLChecker performs all basic ACL checks, but applying - // mask on ACL first. It is useful, when some bits must be always - // set or unset. - MaskedBasicACLChecker struct { - BasicACLChecker - - andMask uint32 - orMask uint32 - } - - nibble struct { - value uint32 - } -) - -const ( - errUnknownRequest = internal.Error("unknown request type") - errUnknownTarget = internal.Error("unknown target type") -) - -const ( - aclFinalBit = 0x10000000 // 29th bit - aclStickyBit = 0x20000000 // 30th bit - - nibbleBBit = 0x1 - nibbleOBit = 0x2 - nibbleSBit = 0x4 - nibbleUBit = 0x8 - - // DefaultAndFilter is a default AND mask of basic ACL value of container. - DefaultAndFilter = 0xFFFFFFFF -) - -var ( - nibbleOffset = map[object.RequestType]uint32{ - object.RequestGet: 0, - object.RequestHead: 1 * 4, - object.RequestPut: 2 * 4, - object.RequestDelete: 3 * 4, - object.RequestSearch: 4 * 4, - object.RequestRange: 5 * 4, - object.RequestRangeHash: 6 * 4, - } -) - -// Action returns true if request is allowed for target. -func (c *BasicACLChecker) Action(rule uint32, req object.RequestType, t acl.Target) (bool, error) { - n, err := fetchNibble(rule, req) - if err != nil { - return false, err - } - - switch t { - case acl.Target_User: - return n.U(), nil - case acl.Target_System: - return n.S(), nil - case acl.Target_Others: - return n.O(), nil - default: - return false, errUnknownTarget - } -} - -// Bearer returns true if bearer token is allowed to use for this request -// as source of extended ACL. -func (c *BasicACLChecker) Bearer(rule uint32, req object.RequestType) (bool, error) { - n, err := fetchNibble(rule, req) - if err != nil { - return false, err - } - - return n.B(), nil -} - -// Extended returns true if extended ACL stored in the container are allowed -// to use. -func (c *BasicACLChecker) Extended(rule uint32) bool { - return rule&aclFinalBit != aclFinalBit -} - -// Sticky returns true if container is not allowed to store objects with -// owners different from request owner. -func (c *BasicACLChecker) Sticky(rule uint32) bool { - return rule&aclStickyBit == aclStickyBit -} - -func fetchNibble(rule uint32, req object.RequestType) (*nibble, error) { - offset, ok := nibbleOffset[req] - if !ok { - return nil, errUnknownRequest - } - - return &nibble{value: (rule >> offset) & 0xf}, nil -} - -// B returns true if `Bearer` bit set in the nibble. -func (n *nibble) B() bool { return n.value&nibbleBBit == nibbleBBit } - -// O returns true if `Others` bit set in the nibble. -func (n *nibble) O() bool { return n.value&nibbleOBit == nibbleOBit } - -// S returns true if `System` bit set in the nibble. -func (n *nibble) S() bool { return n.value&nibbleSBit == nibbleSBit } - -// U returns true if `User` bit set in the nibble. -func (n *nibble) U() bool { return n.value&nibbleUBit == nibbleUBit } - -// NewMaskedBasicACLChecker returns BasicChecker that applies predefined -// bit mask on basic ACL value. -func NewMaskedBasicACLChecker(or, and uint32) BasicChecker { - return MaskedBasicACLChecker{ - BasicACLChecker: BasicACLChecker{}, - andMask: and, - orMask: or, - } -} - -// Action returns true if request is allowed for target. -func (c MaskedBasicACLChecker) Action(rule uint32, req object.RequestType, t acl.Target) (bool, error) { - rule |= c.orMask - rule &= c.andMask - - return c.BasicACLChecker.Action(rule, req, t) -} - -// Bearer returns true if bearer token is allowed to use for this request -// as source of extended ACL. -func (c MaskedBasicACLChecker) Bearer(rule uint32, req object.RequestType) (bool, error) { - rule |= c.orMask - rule &= c.andMask - - return c.BasicACLChecker.Bearer(rule, req) -} - -// Extended returns true if extended ACL stored in the container are allowed -// to use. -func (c MaskedBasicACLChecker) Extended(rule uint32) bool { - rule |= c.orMask - rule &= c.andMask - - return c.BasicACLChecker.Extended(rule) -} - -// Sticky returns true if container is not allowed to store objects with -// owners different from request owner. -func (c MaskedBasicACLChecker) Sticky(rule uint32) bool { - rule |= c.orMask - rule &= c.andMask - - return c.BasicACLChecker.Sticky(rule) -} diff --git a/lib/acl/basic_test.go b/lib/acl/basic_test.go deleted file mode 100644 index b379751f6..000000000 --- a/lib/acl/basic_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package acl - -import ( - "math/bits" - "testing" - - "github.com/nspcc-dev/neofs-api-go/acl" - "github.com/nspcc-dev/neofs-api-go/object" - "github.com/stretchr/testify/require" -) - -func TestBasicACLChecker(t *testing.T) { - reqs := []object.RequestType{ - object.RequestGet, - object.RequestHead, - object.RequestPut, - object.RequestDelete, - object.RequestSearch, - object.RequestRange, - object.RequestRangeHash, - } - - targets := []acl.Target{ - acl.Target_Others, - acl.Target_System, - acl.Target_User, - } - - checker := new(BasicACLChecker) - - t.Run("verb permissions", func(t *testing.T) { - mask := uint32(1) - - for i := range reqs { - res, err := checker.Bearer(mask, reqs[i]) - require.NoError(t, err) - require.True(t, res) - - mask = bits.Reverse32(mask) - res, err = checker.Bearer(mask, reqs[i]) - require.NoError(t, err) - require.False(t, res) - - mask = bits.Reverse32(mask) - - for j := range targets { - mask <<= 1 - res, err = checker.Action(mask, reqs[i], targets[j]) - require.NoError(t, err) - require.True(t, res) - - mask = bits.Reverse32(mask) - res, err = checker.Action(mask, reqs[i], targets[j]) - require.NoError(t, err) - require.False(t, res) - - mask = bits.Reverse32(mask) - } - mask <<= 1 - } - }) - - t.Run("unknown verb", func(t *testing.T) { - mask := uint32(1) - _, err := checker.Bearer(mask, -1) - require.Error(t, err) - - mask = 2 - _, err = checker.Action(mask, -1, acl.Target_Others) - require.Error(t, err) - }) - - t.Run("unknown action", func(t *testing.T) { - mask := uint32(2) - _, err := checker.Action(mask, object.RequestGet, -1) - require.Error(t, err) - }) - - t.Run("extended acl permission", func(t *testing.T) { - // set F-bit - mask := uint32(0) | aclFinalBit - require.False(t, checker.Extended(mask)) - - // unset F-bit - mask = bits.Reverse32(mask) - require.True(t, checker.Extended(mask)) - }) - - t.Run("sticky bit permission", func(t *testing.T) { - mask := uint32(0x20000000) - require.True(t, checker.Sticky(mask)) - - mask = bits.Reverse32(mask) - require.False(t, checker.Sticky(mask)) - }) -} - -// todo: add tests like in basic acl checker -func TestNeoFSMaskedBasicACLChecker(t *testing.T) { - const orFilter = 0x04040444 // this OR filter will be used in neofs-node - checker := NewMaskedBasicACLChecker(orFilter, DefaultAndFilter) - - reqs := []object.RequestType{ - object.RequestGet, - object.RequestHead, - object.RequestPut, - object.RequestSearch, - object.RequestRangeHash, - } - - for i := range reqs { - res, err := checker.Action(0, reqs[i], acl.Target_System) - require.NoError(t, err) - require.True(t, res) - } -} diff --git a/lib/acl/binary.go b/lib/acl/binary.go deleted file mode 100644 index a1cf6e50b..000000000 --- a/lib/acl/binary.go +++ /dev/null @@ -1,129 +0,0 @@ -package acl - -import ( - "context" - "encoding/binary" - "io" - - "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/nspcc-dev/neofs-node/internal" -) - -// BinaryEACLKey is a binary EACL storage key. -type BinaryEACLKey struct { - cid refs.CID -} - -// BinaryEACLValue is a binary EACL storage value. -type BinaryEACLValue struct { - eacl []byte - - sig []byte -} - -// BinaryExtendedACLSource is an interface of storage of binary extended ACL tables with read access. -type BinaryExtendedACLSource interface { - // Must return binary extended ACL table by key. - GetBinaryEACL(context.Context, BinaryEACLKey) (BinaryEACLValue, error) -} - -// BinaryExtendedACLStore is an interface of storage of binary extended ACL tables. -type BinaryExtendedACLStore interface { - BinaryExtendedACLSource - - // Must store binary extended ACL table for key. - PutBinaryEACL(context.Context, BinaryEACLKey, BinaryEACLValue) error -} - -// ErrNilBinaryExtendedACLStore is returned by function that expect a non-nil -// BinaryExtendedACLStore, but received nil. -const ErrNilBinaryExtendedACLStore = internal.Error("binary extended ACL store is nil") - -const sliceLenSize = 4 - -var eaclEndianness = binary.BigEndian - -// CID is a container ID getter. -func (s BinaryEACLKey) CID() refs.CID { - return s.cid -} - -// SetCID is a container ID setter. -func (s *BinaryEACLKey) SetCID(v refs.CID) { - s.cid = v -} - -// EACL is a binary extended ACL table getter. -func (s BinaryEACLValue) EACL() []byte { - return s.eacl -} - -// SetEACL is a binary extended ACL table setter. -func (s *BinaryEACLValue) SetEACL(v []byte) { - s.eacl = v -} - -// Signature is an EACL signature getter. -func (s BinaryEACLValue) Signature() []byte { - return s.sig -} - -// SetSignature is an EACL signature setter. -func (s *BinaryEACLValue) SetSignature(v []byte) { - s.sig = v -} - -// MarshalBinary returns a binary representation of BinaryEACLValue. -func (s BinaryEACLValue) MarshalBinary() ([]byte, error) { - data := make([]byte, sliceLenSize+len(s.eacl)+sliceLenSize+len(s.sig)) - - off := 0 - - eaclEndianness.PutUint32(data[off:], uint32(len(s.eacl))) - off += sliceLenSize - - off += copy(data[off:], s.eacl) - - eaclEndianness.PutUint32(data[off:], uint32(len(s.sig))) - off += sliceLenSize - - copy(data[off:], s.sig) - - return data, nil -} - -// UnmarshalBinary unmarshals BinaryEACLValue from bytes. -func (s *BinaryEACLValue) UnmarshalBinary(data []byte) (err error) { - err = io.ErrUnexpectedEOF - off := 0 - - if len(data[off:]) < sliceLenSize { - return - } - - aclLn := eaclEndianness.Uint32(data[off:]) - off += 4 - - if uint32(len(data[off:])) < aclLn { - return - } - - s.eacl = make([]byte, aclLn) - off += copy(s.eacl, data[off:]) - - if len(data[off:]) < sliceLenSize { - return - } - - sigLn := eaclEndianness.Uint32(data[off:]) - off += 4 - - if uint32(len(data[off:])) < sigLn { - return - } - - s.sig = make([]byte, sigLn) - copy(s.sig, data[off:]) - - return nil -} diff --git a/lib/acl/binary_test.go b/lib/acl/binary_test.go deleted file mode 100644 index eefb59ab5..000000000 --- a/lib/acl/binary_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package acl - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestBinaryEACLValue(t *testing.T) { - s := BinaryEACLValue{} - - eacl := []byte{1, 2, 3} - s.SetEACL(eacl) - require.Equal(t, eacl, s.EACL()) - - sig := []byte{4, 5, 6} - s.SetSignature(sig) - require.Equal(t, sig, s.Signature()) - - data, err := s.MarshalBinary() - require.NoError(t, err) - - s2 := BinaryEACLValue{} - require.NoError(t, s2.UnmarshalBinary(data)) - - require.Equal(t, s, s2) -} diff --git a/lib/acl/extended.go b/lib/acl/extended.go deleted file mode 100644 index 20695bc6e..000000000 --- a/lib/acl/extended.go +++ /dev/null @@ -1,29 +0,0 @@ -package acl - -import ( - "context" - - "github.com/nspcc-dev/neofs-api-go/acl" - "github.com/nspcc-dev/neofs-api-go/refs" -) - -// TypedHeaderSource is a various types of header set interface. -type TypedHeaderSource interface { - // Must return list of Header of particular type. - // Must return false if there is no ability to compose header list. - HeadersOfType(acl.HeaderType) ([]acl.Header, bool) -} - -// ExtendedACLSource is an interface of storage of extended ACL tables with read access. -type ExtendedACLSource interface { - // Must return extended ACL table by container ID key. - GetExtendedACLTable(context.Context, refs.CID) (acl.ExtendedACLTable, error) -} - -// ExtendedACLStore is an interface of storage of extended ACL tables. -type ExtendedACLStore interface { - ExtendedACLSource - - // Must store extended ACL table for container ID key. - PutExtendedACLTable(context.Context, refs.CID, acl.ExtendedACLTable) error -} diff --git a/lib/acl/header.go b/lib/acl/header.go deleted file mode 100644 index 8c779b3b6..000000000 --- a/lib/acl/header.go +++ /dev/null @@ -1,234 +0,0 @@ -package acl - -import ( - "strconv" - - "github.com/nspcc-dev/neofs-api-go/acl" - "github.com/nspcc-dev/neofs-api-go/object" - "github.com/nspcc-dev/neofs-api-go/service" -) - -type objectHeaderSource struct { - obj *object.Object -} - -type typedHeader struct { - n string - v string - t acl.HeaderType -} - -type extendedHeadersWrapper struct { - hdrSrc service.ExtendedHeadersSource -} - -type typedExtendedHeader struct { - hdr service.ExtendedHeader -} - -func newTypedObjSysHdr(name, value string) acl.TypedHeader { - return &typedHeader{ - n: name, - v: value, - t: acl.HdrTypeObjSys, - } -} - -// Name is a name field getter. -func (s typedHeader) Name() string { - return s.n -} - -// Value is a value field getter. -func (s typedHeader) Value() string { - return s.v -} - -// HeaderType is a type field getter. -func (s typedHeader) HeaderType() acl.HeaderType { - return s.t -} - -// TypedHeaderSourceFromObject wraps passed object and returns TypedHeaderSource interface. -func TypedHeaderSourceFromObject(obj *object.Object) TypedHeaderSource { - return &objectHeaderSource{ - obj: obj, - } -} - -// HeaderOfType gathers object headers of passed type and returns Header list. -// -// If value of some header can not be calculated (e.g. nil extended header), it does not appear in list. -// -// Always returns true. -func (s objectHeaderSource) HeadersOfType(typ acl.HeaderType) ([]acl.Header, bool) { - if s.obj == nil { - return nil, true - } - - var res []acl.Header - - switch typ { - case acl.HdrTypeObjUsr: - objHeaders := s.obj.GetHeaders() - - res = make([]acl.Header, 0, len(objHeaders)) // 7 system header fields - - for i := range objHeaders { - if h := newTypedObjectExtendedHeader(objHeaders[i]); h != nil { - res = append(res, h) - } - } - case acl.HdrTypeObjSys: - res = make([]acl.Header, 0, 7) - - sysHdr := s.obj.GetSystemHeader() - - created := sysHdr.GetCreatedAt() - - res = append(res, - // ID - newTypedObjSysHdr( - acl.HdrObjSysNameID, - sysHdr.ID.String(), - ), - - // CID - newTypedObjSysHdr( - acl.HdrObjSysNameCID, - sysHdr.CID.String(), - ), - - // OwnerID - newTypedObjSysHdr( - acl.HdrObjSysNameOwnerID, - sysHdr.OwnerID.String(), - ), - - // Version - newTypedObjSysHdr( - acl.HdrObjSysNameVersion, - strconv.FormatUint(sysHdr.GetVersion(), 10), - ), - - // PayloadLength - newTypedObjSysHdr( - acl.HdrObjSysNamePayloadLength, - strconv.FormatUint(sysHdr.GetPayloadLength(), 10), - ), - - // CreatedAt.UnitTime - newTypedObjSysHdr( - acl.HdrObjSysNameCreatedUnix, - strconv.FormatUint(uint64(created.GetUnixTime()), 10), - ), - - // CreatedAt.Epoch - newTypedObjSysHdr( - acl.HdrObjSysNameCreatedEpoch, - strconv.FormatUint(created.GetEpoch(), 10), - ), - ) - } - - return res, true -} - -func newTypedObjectExtendedHeader(h object.Header) acl.TypedHeader { - val := h.GetValue() - if val == nil { - return nil - } - - res := new(typedHeader) - res.t = acl.HdrTypeObjSys - - switch hdr := val.(type) { - case *object.Header_UserHeader: - if hdr.UserHeader == nil { - return nil - } - - res.t = acl.HdrTypeObjUsr - res.n = hdr.UserHeader.GetKey() - res.v = hdr.UserHeader.GetValue() - case *object.Header_Link: - if hdr.Link == nil { - return nil - } - - switch hdr.Link.GetType() { - case object.Link_Previous: - res.n = acl.HdrObjSysLinkPrev - case object.Link_Next: - res.n = acl.HdrObjSysLinkNext - case object.Link_Child: - res.n = acl.HdrObjSysLinkChild - case object.Link_Parent: - res.n = acl.HdrObjSysLinkPar - case object.Link_StorageGroup: - res.n = acl.HdrObjSysLinkSG - default: - return nil - } - - res.v = hdr.Link.ID.String() - default: - return nil - } - - return res -} - -// TypedHeaderSourceFromExtendedHeaders wraps passed ExtendedHeadersSource and returns TypedHeaderSource interface. -func TypedHeaderSourceFromExtendedHeaders(hdrSrc service.ExtendedHeadersSource) TypedHeaderSource { - return &extendedHeadersWrapper{ - hdrSrc: hdrSrc, - } -} - -// Name returns the result of Key method. -func (s typedExtendedHeader) Name() string { - return s.hdr.Key() -} - -// Value returns the result of Value method. -func (s typedExtendedHeader) Value() string { - return s.hdr.Value() -} - -// HeaderType always returns HdrTypeRequest. -func (s typedExtendedHeader) HeaderType() acl.HeaderType { - return acl.HdrTypeRequest -} - -// TypedHeaders gathers extended request headers and returns TypedHeader list. -// -// Nil headers are ignored. -// -// Always returns true. -func (s extendedHeadersWrapper) HeadersOfType(typ acl.HeaderType) ([]acl.Header, bool) { - if s.hdrSrc == nil { - return nil, true - } - - var res []acl.Header - - if typ == acl.HdrTypeRequest { - hs := s.hdrSrc.ExtendedHeaders() - - res = make([]acl.Header, 0, len(hs)) - - for i := range hs { - if hs[i] == nil { - continue - } - - res = append(res, &typedExtendedHeader{ - hdr: hs[i], - }) - } - } - - return res, true -} diff --git a/lib/acl/headers_test.go b/lib/acl/headers_test.go deleted file mode 100644 index 236e084d2..000000000 --- a/lib/acl/headers_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package acl - -import ( - "testing" - - "github.com/nspcc-dev/neofs-api-go/acl" - "github.com/nspcc-dev/neofs-api-go/object" - "github.com/stretchr/testify/require" -) - -func TestNewTypedObjectExtendedHeader(t *testing.T) { - var res acl.TypedHeader - - hdr := object.Header{} - - // nil value - require.Nil(t, newTypedObjectExtendedHeader(hdr)) - - // UserHeader - { - key := "key" - val := "val" - hdr.Value = &object.Header_UserHeader{ - UserHeader: &object.UserHeader{ - Key: key, - Value: val, - }, - } - - res = newTypedObjectExtendedHeader(hdr) - require.Equal(t, acl.HdrTypeObjUsr, res.HeaderType()) - require.Equal(t, key, res.Name()) - require.Equal(t, val, res.Value()) - } - - { // Link - link := new(object.Link) - link.ID = object.ID{1, 2, 3} - - hdr.Value = &object.Header_Link{ - Link: link, - } - - check := func(lt object.Link_Type, name string) { - link.Type = lt - - res = newTypedObjectExtendedHeader(hdr) - - require.Equal(t, acl.HdrTypeObjSys, res.HeaderType()) - require.Equal(t, name, res.Name()) - require.Equal(t, link.ID.String(), res.Value()) - } - - check(object.Link_Previous, acl.HdrObjSysLinkPrev) - check(object.Link_Next, acl.HdrObjSysLinkNext) - check(object.Link_Parent, acl.HdrObjSysLinkPar) - check(object.Link_Child, acl.HdrObjSysLinkChild) - check(object.Link_StorageGroup, acl.HdrObjSysLinkSG) - } -} diff --git a/lib/acl/match.go b/lib/acl/match.go deleted file mode 100644 index 7d4289cb4..000000000 --- a/lib/acl/match.go +++ /dev/null @@ -1,94 +0,0 @@ -package acl - -import ( - "github.com/nspcc-dev/neofs-api-go/acl" -) - -// Maps MatchType to corresponding function. -// 1st argument of function - header value, 2nd - header filter. -var mMatchFns = map[acl.MatchType]func(acl.Header, acl.Header) bool{ - acl.StringEqual: stringEqual, - - acl.StringNotEqual: stringNotEqual, -} - -const ( - mResUndefined = iota - mResMatch - mResMismatch -) - -// MatchFilters checks if passed source carry at least one header that satisfies passed filters. -// -// Nil header does not satisfy any filter. Any header does not satisfy nil filter. -// -// Returns mResMismatch if passed TypedHeaderSource is nil. -// Returns mResMatch if passed filters are empty. -// -// If headers for some of the HeaderType could not be composed, mResUndefined returns. -func MatchFilters(src TypedHeaderSource, filters []acl.HeaderFilter) int { - if src == nil { - return mResMismatch - } else if len(filters) == 0 { - return mResMatch - } - - matched := 0 - - for _, filter := range filters { - // prevent NPE - if filter == nil { - continue - } - - headers, ok := src.HeadersOfType(filter.HeaderType()) - if !ok { - return mResUndefined - } - - // get headers of filtering type - for _, header := range headers { - // prevent NPE - if header == nil { - continue - } - - // check header name - if header.Name() != filter.Name() { - continue - } - - // get match function - matchFn, ok := mMatchFns[filter.MatchType()] - if !ok { - continue - } - - // check match - if !matchFn(header, filter) { - continue - } - - // increment match counter - matched++ - - break - } - } - - res := mResMismatch - - if matched >= len(filters) { - res = mResMatch - } - - return res -} - -func stringEqual(header, filter acl.Header) bool { - return header.Value() == filter.Value() -} - -func stringNotEqual(header, filter acl.Header) bool { - return header.Value() != filter.Value() -} diff --git a/lib/acl/match_test.go b/lib/acl/match_test.go deleted file mode 100644 index 123f852bc..000000000 --- a/lib/acl/match_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package acl - -import ( - "testing" - - "github.com/nspcc-dev/neofs-api-go/acl" - "github.com/stretchr/testify/require" -) - -type testTypedHeader struct { - t acl.HeaderType - k string - v string -} - -type testHeaderSrc struct { - hs []acl.TypedHeader -} - -type testHeaderFilter struct { - acl.TypedHeader - t acl.MatchType -} - -func (s testHeaderFilter) MatchType() acl.MatchType { - return s.t -} - -func (s testHeaderSrc) HeadersOfType(typ acl.HeaderType) ([]acl.Header, bool) { - res := make([]acl.Header, 0, len(s.hs)) - - for i := range s.hs { - if s.hs[i].HeaderType() == typ { - res = append(res, s.hs[i]) - } - } - - return res, true -} - -func (s testTypedHeader) Name() string { - return s.k -} - -func (s testTypedHeader) Value() string { - return s.v -} - -func (s testTypedHeader) HeaderType() acl.HeaderType { - return s.t -} - -func TestMatchFilters(t *testing.T) { - // nil TypedHeaderSource - require.Equal(t, mResMismatch, MatchFilters(nil, nil)) - - // empty HeaderFilter list - require.Equal(t, mResMatch, MatchFilters(new(testHeaderSrc), nil)) - - k := "key" - v := "value" - ht := acl.HeaderType(1) - - items := []struct { - // list of Key-Value-HeaderType for headers construction - hs []interface{} - // list of Key-Value-HeaderType-MatchType for filters construction - fs []interface{} - exp int - }{ - { // different HeaderType - hs: []interface{}{ - k, v, ht, - }, - fs: []interface{}{ - k, v, ht + 1, acl.StringEqual, - }, - exp: mResMismatch, - }, - { // different keys - hs: []interface{}{ - k, v, ht, - }, - fs: []interface{}{ - k + "1", v, ht, acl.StringEqual, - }, - exp: mResMismatch, - }, - { // equal values, StringEqual - hs: []interface{}{ - k, v, ht, - }, - fs: []interface{}{ - k, v, ht, acl.StringEqual, - }, - exp: mResMatch, - }, - { // equal values, StringNotEqual - hs: []interface{}{ - k, v, ht, - }, - fs: []interface{}{ - k, v, ht, acl.StringNotEqual, - }, - exp: mResMismatch, - }, - { // not equal values, StringEqual - hs: []interface{}{ - k, v, ht, - }, - fs: []interface{}{ - k, v + "1", ht, acl.StringEqual, - }, - exp: mResMismatch, - }, - { // not equal values, StringNotEqual - hs: []interface{}{ - k, v, ht, - }, - fs: []interface{}{ - k, v + "1", ht, acl.StringNotEqual, - }, - exp: mResMatch, - }, - { // one header, two filters - hs: []interface{}{ - k, v, ht, - }, - fs: []interface{}{ - k, v + "1", ht, acl.StringNotEqual, - k, v, ht, acl.StringEqual, - }, - exp: mResMatch, - }, - { // two headers, one filter - hs: []interface{}{ - k, v + "1", ht, - k, v, ht, - }, - fs: []interface{}{ - k, v, ht, acl.StringEqual, - }, - exp: mResMatch, - }, - { - hs: []interface{}{ - k, v + "1", acl.HdrTypeRequest, - k, v, acl.HdrTypeObjUsr, - }, - fs: []interface{}{ - k, v, acl.HdrTypeRequest, acl.StringNotEqual, - k, v, acl.HdrTypeObjUsr, acl.StringEqual, - }, - exp: mResMatch, - }, - } - - for _, item := range items { - headers := make([]acl.TypedHeader, 0) - - for i := 0; i < len(item.hs); i += 3 { - headers = append(headers, &testTypedHeader{ - t: item.hs[i+2].(acl.HeaderType), - k: item.hs[i].(string), - v: item.hs[i+1].(string), - }) - } - - filters := make([]acl.HeaderFilter, 0) - - for i := 0; i < len(item.fs); i += 4 { - filters = append(filters, &testHeaderFilter{ - TypedHeader: &testTypedHeader{ - t: item.fs[i+2].(acl.HeaderType), - k: item.fs[i].(string), - v: item.fs[i+1].(string), - }, - t: item.fs[i+3].(acl.MatchType), - }) - } - - require.Equal(t, - item.exp, - MatchFilters( - &testHeaderSrc{ - hs: headers, - }, - filters, - ), - ) - } -} diff --git a/lib/boot/bootstrap_test.go b/lib/boot/bootstrap_test.go deleted file mode 100644 index 206e2562e..000000000 --- a/lib/boot/bootstrap_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package boot - -import ( - "testing" - - "github.com/nspcc-dev/neofs-api-go/bootstrap" - "github.com/stretchr/testify/require" -) - -func TestBootstrapPeerParams(t *testing.T) { - s := BootstrapPeerParams{} - - nodeInfo := &bootstrap.NodeInfo{ - Address: "address", - PubKey: []byte{1, 2, 3}, - Options: []string{ - "opt1", - "opt2", - }, - } - s.SetNodeInfo(nodeInfo) - - require.Equal(t, nodeInfo, s.NodeInfo()) -} diff --git a/lib/boot/bootstrapper.go b/lib/boot/bootstrapper.go deleted file mode 100644 index f97e6a789..000000000 --- a/lib/boot/bootstrapper.go +++ /dev/null @@ -1,31 +0,0 @@ -package boot - -import ( - "github.com/nspcc-dev/neofs-api-go/bootstrap" - "github.com/nspcc-dev/neofs-node/internal" -) - -// BootstrapPeerParams is a group of parameters -// for storage node bootstrap. -type BootstrapPeerParams struct { - info *bootstrap.NodeInfo -} - -// PeerBootstrapper is an interface of the NeoFS node bootstrap tool. -type PeerBootstrapper interface { - AddPeer(BootstrapPeerParams) error -} - -// ErrNilPeerBootstrapper is returned by functions that expect -// a non-nil PeerBootstrapper, but received nil. -const ErrNilPeerBootstrapper = internal.Error("peer bootstrapper is nil") - -// SetNodeInfo is a node info setter. -func (s *BootstrapPeerParams) SetNodeInfo(v *bootstrap.NodeInfo) { - s.info = v -} - -// NodeInfo is a node info getter. -func (s BootstrapPeerParams) NodeInfo() *bootstrap.NodeInfo { - return s.info -} diff --git a/lib/boot/storage.go b/lib/boot/storage.go deleted file mode 100644 index 9043576ce..000000000 --- a/lib/boot/storage.go +++ /dev/null @@ -1,46 +0,0 @@ -package boot - -import ( - "context" - - "go.uber.org/zap" -) - -// StorageBootParams is a group of parameters -// for storage node bootstrap operation. -type StorageBootParams struct { - BootstrapPeerParams -} - -// StorageBootController is an entity that performs -// registration of a storage node in NeoFS network. -type StorageBootController struct { - peerBoot PeerBootstrapper - - bootPrm StorageBootParams - - log *zap.Logger -} - -// SetPeerBootstrapper is a PeerBootstrapper setter. -func (s *StorageBootController) SetPeerBootstrapper(v PeerBootstrapper) { - s.peerBoot = v -} - -// SetBootParams is a storage node bootstrap parameters setter. -func (s *StorageBootController) SetBootParams(v StorageBootParams) { - s.bootPrm = v -} - -// SetLogger is a logging component setter. -func (s *StorageBootController) SetLogger(v *zap.Logger) { - s.log = v -} - -// Bootstrap registers storage node in NeoFS system. -func (s StorageBootController) Bootstrap(context.Context) { - // register peer in NeoFS network - if err := s.peerBoot.AddPeer(s.bootPrm.BootstrapPeerParams); err != nil && s.log != nil { - s.log.Error("could not register storage node in network") - } -} diff --git a/lib/buckets/boltdb/methods_test.go b/lib/buckets/boltdb/methods_test.go deleted file mode 100644 index dc9517d73..000000000 --- a/lib/buckets/boltdb/methods_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package boltdb - -import ( - "encoding/binary" - "io/ioutil" - "os" - "strings" - "testing" - "time" - - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/pkg/errors" - "github.com/spf13/viper" - "github.com/stretchr/testify/require" -) - -var config = strings.NewReader(` -storage: - test_bucket: - bucket: boltdb - path: ./temp/storage/test_bucket - perm: 0777 -`) - -func TestBucket(t *testing.T) { - file, err := ioutil.TempFile("", "test_bolt_db") - require.NoError(t, err) - require.NoError(t, file.Close()) - - v := viper.New() - require.NoError(t, v.ReadConfig(config)) - - // -- // - _, err = NewOptions("storage.test_bucket", v) - require.EqualError(t, err, errEmptyPath.Error()) - - v.SetDefault("storage.test_bucket.path", file.Name()) - v.SetDefault("storage.test_bucket.timeout", time.Millisecond*100) - // -- // - - opts, err := NewOptions("storage.test_bucket", v) - require.NoError(t, err) - - db, err := NewBucket(&opts) - require.NoError(t, err) - - require.NotPanics(t, func() { db.Size() }) - - var ( - count = uint64(10) - expected = []byte("test") - ) - - for i := uint64(0); i < count; i++ { - key := make([]byte, 8) - binary.BigEndian.PutUint64(key, i) - - require.False(t, db.Has(key)) - - val, err := db.Get(key) - require.EqualError(t, errors.Cause(err), core.ErrNotFound.Error()) - require.Empty(t, val) - - require.NoError(t, db.Set(key, expected)) - - require.True(t, db.Has(key)) - - val, err = db.Get(key) - require.NoError(t, err) - require.Equal(t, expected, val) - - keys, err := db.List() - require.NoError(t, err) - require.Len(t, keys, 1) - require.Equal(t, key, keys[0]) - - require.EqualError(t, db.Iterate(nil), core.ErrNilFilterHandler.Error()) - - items, err := core.ListBucketItems(db, func(_, _ []byte) bool { return true }) - require.NoError(t, err) - require.Len(t, items, 1) - require.Equal(t, key, items[0].Key) - require.Equal(t, val, items[0].Val) - - require.NoError(t, db.Del(key)) - require.False(t, db.Has(key)) - - val, err = db.Get(key) - require.EqualError(t, errors.Cause(err), core.ErrNotFound.Error()) - require.Empty(t, val) - } - - require.NoError(t, db.Close()) - require.NoError(t, os.RemoveAll(file.Name())) -} diff --git a/lib/buckets/boltdb/plugin/main.go b/lib/buckets/boltdb/plugin/main.go deleted file mode 100644 index 04a8f9f22..000000000 --- a/lib/buckets/boltdb/plugin/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "github.com/nspcc-dev/neofs-node/lib/buckets/boltdb" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/pkg/errors" - "github.com/spf13/viper" -) - -var _ = PrepareBucket - -// PrepareBucket is interface method for bucket. -func PrepareBucket(name core.BucketType, v *viper.Viper) (db core.Bucket, err error) { - var opts boltdb.Options - - if opts, err = boltdb.NewOptions("storage."+name, v); err != nil { - err = errors.Wrapf(err, "%q: could not prepare options", name) - return - } else if db, err = boltdb.NewBucket(&opts); err != nil { - err = errors.Wrapf(err, "%q: could not prepare bucket", name) - return - } - - return -} diff --git a/lib/buckets/init.go b/lib/buckets/init.go deleted file mode 100644 index ea4c5756d..000000000 --- a/lib/buckets/init.go +++ /dev/null @@ -1,64 +0,0 @@ -package buckets - -import ( - "plugin" - "strings" - - "github.com/nspcc-dev/neofs-node/lib/buckets/boltdb" - "github.com/nspcc-dev/neofs-node/lib/buckets/fsbucket" - "github.com/nspcc-dev/neofs-node/lib/buckets/inmemory" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/pkg/errors" - "github.com/spf13/viper" - "go.uber.org/zap" -) - -const ( - // BoltDBBucket is a name of BoltDB bucket. - BoltDBBucket = "boltdb" - - // InMemoryBucket is a name RAM bucket. - InMemoryBucket = "in-memory" - - // FileSystemBucket is a name of file system bucket. - FileSystemBucket = "fsbucket" - - bucketSymbol = "PrepareBucket" -) - -// NewBucket is a bucket's constructor. -func NewBucket(name core.BucketType, l *zap.Logger, v *viper.Viper) (core.Bucket, error) { - bucket := v.GetString("storage." + string(name) + ".bucket") - - l.Info("initialize bucket", - zap.String("name", string(name)), - zap.String("bucket", bucket)) - - switch strings.ToLower(bucket) { - case FileSystemBucket: - return fsbucket.NewBucket(name, v) - - case InMemoryBucket: - return inmemory.NewBucket(name, v), nil - - case BoltDBBucket: - opts, err := boltdb.NewOptions("storage."+name, v) - if err != nil { - return nil, err - } - - return boltdb.NewBucket(&opts) - default: - instance, err := plugin.Open(bucket) - if err != nil { - return nil, errors.Wrapf(err, "could not load bucket: `%s`", bucket) - } - - sym, err := instance.Lookup(bucketSymbol) - if err != nil { - return nil, errors.Wrapf(err, "could not find bucket signature: `%s`", bucket) - } - - return sym.(func(core.BucketType, *viper.Viper) (core.Bucket, error))(name, v) - } -} diff --git a/lib/buckets/inmemory/bucket.go b/lib/buckets/inmemory/bucket.go deleted file mode 100644 index b5f48316c..000000000 --- a/lib/buckets/inmemory/bucket.go +++ /dev/null @@ -1,60 +0,0 @@ -package inmemory - -import ( - "sync" - - "github.com/mr-tron/base58" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/spf13/viper" -) - -type ( - bucket struct { - *sync.RWMutex - items map[string][]byte - } -) - -const ( - defaultCapacity = 100 -) - -var ( - _ core.Bucket = (*bucket)(nil) - - // for in usage - _ = NewBucket -) - -func stringifyKey(key []byte) string { - return base58.Encode(key) -} - -func decodeKey(key string) []byte { - k, err := base58.Decode(key) - if err != nil { - panic(err) // it can fail only for not base58 strings - } - - return k -} - -func makeCopy(val []byte) []byte { - tmp := make([]byte, len(val)) - copy(tmp, val) - - return tmp -} - -// NewBucket creates new in-memory bucket instance. -func NewBucket(name core.BucketType, v *viper.Viper) core.Bucket { - var capacity int - if capacity = v.GetInt("storage." + string(name) + ".capacity"); capacity <= 0 { - capacity = defaultCapacity - } - - return &bucket{ - RWMutex: new(sync.RWMutex), - items: make(map[string][]byte, capacity), - } -} diff --git a/lib/buckets/inmemory/methods.go b/lib/buckets/inmemory/methods.go deleted file mode 100644 index 7e1685c70..000000000 --- a/lib/buckets/inmemory/methods.go +++ /dev/null @@ -1,107 +0,0 @@ -package inmemory - -import ( - "unsafe" - - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/pkg/errors" -) - -// Get value by key. -func (b *bucket) Get(key []byte) ([]byte, error) { - k := stringifyKey(key) - - b.RLock() - val, ok := b.items[k] - result := makeCopy(val) - b.RUnlock() - - if !ok { - return nil, errors.Wrapf(core.ErrNotFound, "key=`%s`", k) - } - - return result, nil -} - -// Set value by key. -func (b *bucket) Set(key, value []byte) error { - k := stringifyKey(key) - - b.Lock() - b.items[k] = makeCopy(value) - b.Unlock() - - return nil -} - -// Del value by key. -func (b *bucket) Del(key []byte) error { - k := stringifyKey(key) - - b.Lock() - delete(b.items, k) - b.Unlock() - - return nil -} - -// Has checks key exists. -func (b *bucket) Has(key []byte) bool { - k := stringifyKey(key) - - b.RLock() - _, ok := b.items[k] - b.RUnlock() - - return ok -} - -// Size size of bucket. -func (b *bucket) Size() int64 { - b.RLock() - // TODO we must replace in future - size := unsafe.Sizeof(b.items) - b.RUnlock() - - return int64(size) -} - -func (b *bucket) List() ([][]byte, error) { - var result = make([][]byte, 0) - - b.RLock() - for key := range b.items { - result = append(result, decodeKey(key)) - } - b.RUnlock() - - return result, nil -} - -// Filter items by closure. -func (b *bucket) Iterate(handler core.FilterHandler) error { - if handler == nil { - return core.ErrNilFilterHandler - } - - b.RLock() - for key, val := range b.items { - k, v := decodeKey(key), makeCopy(val) - - if !handler(k, v) { - return core.ErrIteratingAborted - } - } - b.RUnlock() - - return nil -} - -// Close bucket (just empty). -func (b *bucket) Close() error { - b.Lock() - b.items = make(map[string][]byte) - b.Unlock() - - return nil -} diff --git a/lib/container/alias.go b/lib/container/alias.go deleted file mode 100644 index cb2cdf3c6..000000000 --- a/lib/container/alias.go +++ /dev/null @@ -1,15 +0,0 @@ -package container - -import ( - "github.com/nspcc-dev/neofs-api-go/container" - "github.com/nspcc-dev/neofs-api-go/refs" -) - -// Container is a type alias of Container. -type Container = container.Container - -// CID is a type alias of CID. -type CID = refs.CID - -// OwnerID is a type alias of OwnerID. -type OwnerID = refs.OwnerID diff --git a/lib/container/storage.go b/lib/container/storage.go deleted file mode 100644 index 5192a3b2e..000000000 --- a/lib/container/storage.go +++ /dev/null @@ -1,134 +0,0 @@ -package container - -import ( - "context" -) - -// GetParams is a group of parameters for container receiving operation. -type GetParams struct { - ctxValue - - cidValue -} - -// GetResult is a group of values returned by container receiving operation. -type GetResult struct { - cnrValue -} - -// PutParams is a group of parameters for container storing operation. -type PutParams struct { - ctxValue - - cnrValue -} - -// PutResult is a group of values returned by container storing operation. -type PutResult struct { - cidValue -} - -// DeleteParams is a group of parameters for container removal operation. -type DeleteParams struct { - ctxValue - - cidValue - - ownerID OwnerID -} - -// DeleteResult is a group of values returned by container removal operation. -type DeleteResult struct{} - -// ListParams is a group of parameters for container listing operation. -type ListParams struct { - ctxValue - - ownerIDList []OwnerID -} - -// ListResult is a group of values returned by container listing operation. -type ListResult struct { - cidList []CID -} - -type cnrValue struct { - cnr *Container -} - -type cidValue struct { - cid CID -} - -type ctxValue struct { - ctx context.Context -} - -// Storage is an interface of the storage of NeoFS containers. -type Storage interface { - GetContainer(GetParams) (*GetResult, error) - PutContainer(PutParams) (*PutResult, error) - DeleteContainer(DeleteParams) (*DeleteResult, error) - ListContainers(ListParams) (*ListResult, error) - // TODO: add EACL methods -} - -// Context is a context getter. -func (s ctxValue) Context() context.Context { - return s.ctx -} - -// SetContext is a context setter. -func (s *ctxValue) SetContext(v context.Context) { - s.ctx = v -} - -// CID is a container ID getter. -func (s cidValue) CID() CID { - return s.cid -} - -// SetCID is a container ID getter. -func (s *cidValue) SetCID(v CID) { - s.cid = v -} - -// Container is a container getter. -func (s cnrValue) Container() *Container { - return s.cnr -} - -// SetContainer is a container setter. -func (s *cnrValue) SetContainer(v *Container) { - s.cnr = v -} - -// OwnerID is an owner ID getter. -func (s DeleteParams) OwnerID() OwnerID { - return s.ownerID -} - -// SetOwnerID is an owner ID setter. -func (s *DeleteParams) SetOwnerID(v OwnerID) { - s.ownerID = v -} - -// OwnerIDList is an owner ID list getter. -func (s ListParams) OwnerIDList() []OwnerID { - return s.ownerIDList -} - -// SetOwnerIDList is an owner ID list setter. -func (s *ListParams) SetOwnerIDList(v ...OwnerID) { - s.ownerIDList = v -} - -// CIDList is a container ID list getter. -func (s ListResult) CIDList() []CID { - return s.cidList -} - -// SetCIDList is a container ID list setter. -func (s *ListResult) SetCIDList(v []CID) { - s.cidList = v -} diff --git a/lib/container/storage_test.go b/lib/container/storage_test.go deleted file mode 100644 index 77f386514..000000000 --- a/lib/container/storage_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package container - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGetParams(t *testing.T) { - p := new(GetParams) - - cid := CID{1, 2, 3} - p.SetCID(cid) - - require.Equal(t, cid, p.CID()) -} - -func TestGetResult(t *testing.T) { - r := new(GetResult) - - cnr := &Container{ - OwnerID: OwnerID{1, 2, 3}, - } - r.SetContainer(cnr) - - require.Equal(t, cnr, r.Container()) -} - -func TestPutParams(t *testing.T) { - p := new(PutParams) - - cnr := &Container{ - OwnerID: OwnerID{1, 2, 3}, - } - p.SetContainer(cnr) - - require.Equal(t, cnr, p.Container()) -} - -func TestPutResult(t *testing.T) { - r := new(PutResult) - - cid := CID{1, 2, 3} - r.SetCID(cid) - - require.Equal(t, cid, r.CID()) -} - -func TestDeleteParams(t *testing.T) { - p := new(DeleteParams) - - ownerID := OwnerID{1, 2, 3} - p.SetOwnerID(ownerID) - require.Equal(t, ownerID, p.OwnerID()) - - cid := CID{4, 5, 6} - p.SetCID(cid) - require.Equal(t, cid, p.CID()) -} - -func TestListParams(t *testing.T) { - p := new(ListParams) - - ownerIDList := []OwnerID{ - {1, 2, 3}, - {4, 5, 6}, - } - p.SetOwnerIDList(ownerIDList...) - - require.Equal(t, ownerIDList, p.OwnerIDList()) -} - -func TestListResult(t *testing.T) { - r := new(ListResult) - - cidList := []CID{ - {1, 2, 3}, - {4, 5, 6}, - } - r.SetCIDList(cidList) - - require.Equal(t, cidList, r.CIDList()) -} diff --git a/lib/core/storage.go b/lib/core/storage.go deleted file mode 100644 index 27e22f6d7..000000000 --- a/lib/core/storage.go +++ /dev/null @@ -1,94 +0,0 @@ -package core - -import ( - "github.com/nspcc-dev/neofs-node/internal" - "github.com/pkg/errors" -) - -type ( - // BucketType is name of bucket - BucketType string - - // FilterHandler where you receive key/val in your closure - FilterHandler func(key, val []byte) bool - - // BucketItem used in filter - BucketItem struct { - Key []byte - Val []byte - } - - // Bucket is sub-store interface - Bucket interface { - Get(key []byte) ([]byte, error) - Set(key, value []byte) error - Del(key []byte) error - Has(key []byte) bool - Size() int64 - List() ([][]byte, error) - Iterate(FilterHandler) error - // Steam can be implemented by badger.Stream, but not for now - // Stream(ctx context.Context, key []byte, cb func(io.ReadWriter) error) error - Close() error - } - - // Storage component interface - Storage interface { - GetBucket(name BucketType) (Bucket, error) - Size() int64 - Close() error - } -) - -const ( - // BlobStore is a blob bucket name. - BlobStore BucketType = "blob" - - // MetaStore is a meta bucket name. - MetaStore BucketType = "meta" - - // SpaceMetricsStore is a space metrics bucket name. - SpaceMetricsStore BucketType = "space-metrics" -) - -var ( - // ErrNilFilterHandler when FilterHandler is empty - ErrNilFilterHandler = errors.New("handler can't be nil") - - // ErrNotFound is returned by key-value storage methods - // that could not find element by key. - ErrNotFound = internal.Error("key not found") -) - -// ErrIteratingAborted is returned by storage iterator -// after iteration has been interrupted. -var ErrIteratingAborted = errors.New("iteration aborted") - -var errEmptyBucket = errors.New("empty bucket") - -func (t BucketType) String() string { return string(t) } - -// ListBucketItems performs iteration over Bucket and returns the full list of its items. -func ListBucketItems(b Bucket, h FilterHandler) ([]BucketItem, error) { - if b == nil { - return nil, errEmptyBucket - } else if h == nil { - return nil, ErrNilFilterHandler - } - - items := make([]BucketItem, 0) - - if err := b.Iterate(func(key, val []byte) bool { - if h(key, val) { - items = append(items, BucketItem{ - Key: key, - Val: val, - }) - } - return true - }); err != nil { - return nil, err - } - - return items, nil -} diff --git a/lib/core/storage_test.go b/lib/core/storage_test.go deleted file mode 100644 index a4b451117..000000000 --- a/lib/core/storage_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package core - -import ( - "crypto/rand" - "testing" - - "github.com/stretchr/testify/require" -) - -type testBucket struct { - Bucket - - items []BucketItem -} - -func (s *testBucket) Iterate(f FilterHandler) error { - for i := range s.items { - if !f(s.items[i].Key, s.items[i].Val) { - return ErrIteratingAborted - } - } - - return nil -} - -func TestListBucketItems(t *testing.T) { - _, err := ListBucketItems(nil, nil) - require.EqualError(t, err, errEmptyBucket.Error()) - - b := new(testBucket) - - _, err = ListBucketItems(b, nil) - require.EqualError(t, err, ErrNilFilterHandler.Error()) - - var ( - count = 10 - ln = 10 - items = make([]BucketItem, 0, count) - ) - - for i := 0; i < count; i++ { - items = append(items, BucketItem{ - Key: testData(t, ln), - Val: testData(t, ln), - }) - } - - b.items = items - - res, err := ListBucketItems(b, func(key, val []byte) bool { return true }) - require.NoError(t, err) - require.Equal(t, items, res) - - res, err = ListBucketItems(b, func(key, val []byte) bool { return false }) - require.NoError(t, err) - require.Empty(t, res) -} - -func testData(t *testing.T, sz int) []byte { - d := make([]byte, sz) - _, err := rand.Read(d) - require.NoError(t, err) - - return d -} diff --git a/lib/implementations/acl.go b/lib/implementations/acl.go deleted file mode 100644 index ce3fd58ad..000000000 --- a/lib/implementations/acl.go +++ /dev/null @@ -1,392 +0,0 @@ -package implementations - -import ( - "context" - - sc "github.com/nspcc-dev/neo-go/pkg/smartcontract" - libacl "github.com/nspcc-dev/neofs-api-go/acl" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/acl" - "github.com/nspcc-dev/neofs-node/lib/blockchain/goclient" - "github.com/nspcc-dev/neofs-node/lib/container" - - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/pkg/errors" -) - -// Consider moving ACLHelper implementation to the ACL library. - -type ( - // ACLHelper is an interface, that provides useful functions - // for ACL object pre-processor. - ACLHelper interface { - BasicACLGetter - ContainerOwnerChecker - } - - // BasicACLGetter helper provides function to return basic ACL value. - BasicACLGetter interface { - GetBasicACL(context.Context, CID) (uint32, error) - } - - // ContainerOwnerChecker checks owner of the container. - ContainerOwnerChecker interface { - IsContainerOwner(context.Context, CID, refs.OwnerID) (bool, error) - } - - aclHelper struct { - cnr container.Storage - } -) - -type binaryEACLSource struct { - binaryStore acl.BinaryExtendedACLSource -} - -// StaticContractClient is a wrapper over Neo:Morph client -// that invokes single smart contract methods with fixed fee. -type StaticContractClient struct { - // neo-go client instance - client *goclient.Client - - // contract script-hash - scScriptHash util.Uint160 - - // invocation fee - fee util.Fixed8 -} - -// MorphContainerContract is a wrapper over StaticContractClient -// for Container contract calls. -type MorphContainerContract struct { - // NeoFS Container smart-contract - containerContract StaticContractClient - - // set EACL method name of container contract - eaclSetMethodName string - - // get EACL method name of container contract - eaclGetMethodName string - - // get container method name of container contract - cnrGetMethodName string - - // put container method name of container contract - cnrPutMethodName string - - // delete container method name of container contract - cnrDelMethodName string - - // list containers method name of container contract - cnrListMethodName string -} - -const ( - errNewACLHelper = internal.Error("cannot create ACLHelper instance") -) - -// GetBasicACL returns basic ACL of the container. -func (h aclHelper) GetBasicACL(ctx context.Context, cid CID) (uint32, error) { - gp := container.GetParams{} - gp.SetContext(ctx) - gp.SetCID(cid) - - gResp, err := h.cnr.GetContainer(gp) - if err != nil { - return 0, err - } - - return gResp.Container().BasicACL, nil -} - -// IsContainerOwner returns true if provided id is an owner container. -func (h aclHelper) IsContainerOwner(ctx context.Context, cid CID, id refs.OwnerID) (bool, error) { - gp := container.GetParams{} - gp.SetContext(ctx) - gp.SetCID(cid) - - gResp, err := h.cnr.GetContainer(gp) - if err != nil { - return false, err - } - - return gResp.Container().OwnerID.Equal(id), nil -} - -// NewACLHelper returns implementation of the ACLHelper interface. -func NewACLHelper(cnr container.Storage) (ACLHelper, error) { - if cnr == nil { - return nil, errNewACLHelper - } - - return aclHelper{cnr}, nil -} - -// ExtendedACLSourceFromBinary wraps BinaryExtendedACLSource and returns ExtendedACLSource. -// -// If passed BinaryExtendedACLSource is nil, acl.ErrNilBinaryExtendedACLStore returns. -func ExtendedACLSourceFromBinary(v acl.BinaryExtendedACLSource) (acl.ExtendedACLSource, error) { - if v == nil { - return nil, acl.ErrNilBinaryExtendedACLStore - } - - return &binaryEACLSource{ - binaryStore: v, - }, nil -} - -// GetExtendedACLTable receives eACL table in a binary representation from storage, -// unmarshals it and returns ExtendedACLTable interface. -func (s binaryEACLSource) GetExtendedACLTable(ctx context.Context, cid refs.CID) (libacl.ExtendedACLTable, error) { - key := acl.BinaryEACLKey{} - key.SetCID(cid) - - val, err := s.binaryStore.GetBinaryEACL(ctx, key) - if err != nil { - return nil, err - } - - eacl := val.EACL() - - // TODO: verify signature - - res := libacl.WrapEACLTable(nil) - - return res, res.UnmarshalBinary(eacl) -} - -// NewStaticContractClient initializes a new StaticContractClient. -// -// If passed Client is nil, goclient.ErrNilClient returns. -func NewStaticContractClient(client *goclient.Client, scHash util.Uint160, fee util.Fixed8) (StaticContractClient, error) { - res := StaticContractClient{ - client: client, - scScriptHash: scHash, - fee: fee, - } - - var err error - if client == nil { - err = goclient.ErrNilClient - } - - return res, err -} - -// Invoke calls Invoke method of goclient with predefined script hash and fee. -// Supported args types are the same as in goclient. -// -// If Client is not initialized, goclient.ErrNilClient returns. -func (s StaticContractClient) Invoke(method string, args ...interface{}) error { - if s.client == nil { - return goclient.ErrNilClient - } - - return s.client.Invoke( - s.scScriptHash, - s.fee, - method, - args..., - ) -} - -// TestInvoke calls TestInvoke method of goclient with predefined script hash. -// -// If Client is not initialized, goclient.ErrNilClient returns. -func (s StaticContractClient) TestInvoke(method string, args ...interface{}) ([]sc.Parameter, error) { - if s.client == nil { - return nil, goclient.ErrNilClient - } - - return s.client.TestInvoke( - s.scScriptHash, - method, - args..., - ) -} - -// SetContainerContractClient is a container contract client setter. -func (s *MorphContainerContract) SetContainerContractClient(v StaticContractClient) { - s.containerContract = v -} - -// SetEACLGetMethodName is a container contract Get EACL method name setter. -func (s *MorphContainerContract) SetEACLGetMethodName(v string) { - s.eaclGetMethodName = v -} - -// SetEACLSetMethodName is a container contract Set EACL method name setter. -func (s *MorphContainerContract) SetEACLSetMethodName(v string) { - s.eaclSetMethodName = v -} - -// SetContainerGetMethodName is a container contract Get method name setter. -func (s *MorphContainerContract) SetContainerGetMethodName(v string) { - s.cnrGetMethodName = v -} - -// SetContainerPutMethodName is a container contract Put method name setter. -func (s *MorphContainerContract) SetContainerPutMethodName(v string) { - s.cnrPutMethodName = v -} - -// SetContainerDeleteMethodName is a container contract Delete method name setter. -func (s *MorphContainerContract) SetContainerDeleteMethodName(v string) { - s.cnrDelMethodName = v -} - -// SetContainerListMethodName is a container contract List method name setter. -func (s *MorphContainerContract) SetContainerListMethodName(v string) { - s.cnrListMethodName = v -} - -// GetBinaryEACL performs the test invocation call of GetEACL method of NeoFS Container contract. -func (s *MorphContainerContract) GetBinaryEACL(_ context.Context, key acl.BinaryEACLKey) (acl.BinaryEACLValue, error) { - res := acl.BinaryEACLValue{} - - prms, err := s.containerContract.TestInvoke( - s.eaclGetMethodName, - key.CID().Bytes(), - ) - if err != nil { - return res, err - } else if ln := len(prms); ln != 1 { - return res, errors.Errorf("unexpected stack parameter count: %d", ln) - } - - eacl, err := goclient.BytesFromStackParameter(prms[0]) - if err == nil { - res.SetEACL(eacl) - } - - return res, err -} - -// PutBinaryEACL invokes the call of SetEACL method of NeoFS Container contract. -func (s *MorphContainerContract) PutBinaryEACL(_ context.Context, key acl.BinaryEACLKey, val acl.BinaryEACLValue) error { - return s.containerContract.Invoke( - s.eaclSetMethodName, - key.CID().Bytes(), - val.EACL(), - val.Signature(), - ) -} - -// GetContainer performs the test invocation call of Get method of NeoFS Container contract. -func (s *MorphContainerContract) GetContainer(p container.GetParams) (*container.GetResult, error) { - prms, err := s.containerContract.TestInvoke( - s.cnrGetMethodName, - p.CID().Bytes(), - ) - if err != nil { - return nil, errors.Wrap(err, "could not perform test invocation") - } else if ln := len(prms); ln != 1 { - return nil, errors.Errorf("unexpected stack item count: %d", ln) - } - - cnrBytes, err := goclient.BytesFromStackParameter(prms[0]) - if err != nil { - return nil, errors.Wrap(err, "could not get byte array from stack item") - } - - cnr := new(container.Container) - if err := cnr.Unmarshal(cnrBytes); err != nil { - return nil, errors.Wrap(err, "could not unmarshal container from bytes") - } - - res := new(container.GetResult) - res.SetContainer(cnr) - - return res, nil -} - -// PutContainer invokes the call of Put method of NeoFS Container contract. -func (s *MorphContainerContract) PutContainer(p container.PutParams) (*container.PutResult, error) { - cnr := p.Container() - - cid, err := cnr.ID() - if err != nil { - return nil, errors.Wrap(err, "could not calculate container ID") - } - - cnrBytes, err := cnr.Marshal() - if err != nil { - return nil, errors.Wrap(err, "could not marshal container") - } - - if err := s.containerContract.Invoke( - s.cnrPutMethodName, - cnr.OwnerID.Bytes(), - cnrBytes, - []byte{}, - ); err != nil { - return nil, errors.Wrap(err, "could not invoke contract method") - } - - res := new(container.PutResult) - res.SetCID(cid) - - return res, nil -} - -// DeleteContainer invokes the call of Delete method of NeoFS Container contract. -func (s *MorphContainerContract) DeleteContainer(p container.DeleteParams) (*container.DeleteResult, error) { - if err := s.containerContract.Invoke( - s.cnrDelMethodName, - p.CID().Bytes(), - p.OwnerID().Bytes(), - []byte{}, - ); err != nil { - return nil, errors.Wrap(err, "could not invoke contract method") - } - - return new(container.DeleteResult), nil -} - -// ListContainers performs the test invocation call of Get method of NeoFS Container contract. -// -// If owner ID list in parameters is non-empty, bytes of first owner are attached to call. -func (s *MorphContainerContract) ListContainers(p container.ListParams) (*container.ListResult, error) { - args := make([]interface{}, 0, 1) - - if ownerIDList := p.OwnerIDList(); len(ownerIDList) > 0 { - args = append(args, ownerIDList[0].Bytes()) - } - - prms, err := s.containerContract.TestInvoke( - s.cnrListMethodName, - args..., - ) - if err != nil { - return nil, errors.Wrap(err, "could not perform test invocation") - } else if ln := len(prms); ln != 1 { - return nil, errors.Errorf("unexpected stack item count: %d", ln) - } - - prms, err = goclient.ArrayFromStackParameter(prms[0]) - if err != nil { - return nil, errors.Wrap(err, "could not get stack item array from stack item") - } - - cidList := make([]CID, 0, len(prms)) - - for i := range prms { - cidBytes, err := goclient.BytesFromStackParameter(prms[i]) - if err != nil { - return nil, errors.Wrap(err, "could not get byte array from stack item") - } - - cid, err := refs.CIDFromBytes(cidBytes) - if err != nil { - return nil, errors.Wrap(err, "could not get container ID from bytes") - } - - cidList = append(cidList, cid) - } - - res := new(container.ListResult) - res.SetCIDList(cidList) - - return res, nil -} diff --git a/lib/implementations/acl_test.go b/lib/implementations/acl_test.go deleted file mode 100644 index cb462de74..000000000 --- a/lib/implementations/acl_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package implementations - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestStaticContractClient(t *testing.T) { - s := new(StaticContractClient) - - require.NotPanics(t, func() { - _, _ = s.TestInvoke("") - }) - - require.NotPanics(t, func() { - _ = s.Invoke("") - }) -} diff --git a/lib/implementations/balance.go b/lib/implementations/balance.go deleted file mode 100644 index d535c0eaf..000000000 --- a/lib/implementations/balance.go +++ /dev/null @@ -1,141 +0,0 @@ -package implementations - -import ( - "github.com/nspcc-dev/neo-go/pkg/encoding/address" - "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/nspcc-dev/neofs-node/lib/blockchain/goclient" - "github.com/pkg/errors" -) - -// MorphBalanceContract is a wrapper over NeoFS Balance contract client -// that provides an interface of manipulations with user funds. -type MorphBalanceContract struct { - // NeoFS Balance smart-contract - balanceContract StaticContractClient - - // "balance of" method name of balance contract - balanceOfMethodName string - - // decimals method name of balance contract - decimalsMethodName string -} - -// BalanceOfParams is a structure that groups the parameters -// for NeoFS user balance receiving operation. -type BalanceOfParams struct { - owner refs.OwnerID -} - -// BalanceOfResult is a structure that groups the values -// of the result of NeoFS user balance receiving operation. -type BalanceOfResult struct { - amount int64 -} - -// DecimalsParams is a structure that groups the parameters -// for NeoFS token decimals receiving operation. -type DecimalsParams struct { -} - -// DecimalsResult is a structure that groups the values -// of the result of NeoFS token decimals receiving operation. -type DecimalsResult struct { - dec int64 -} - -// SetBalanceContractClient is a Balance contract client setter. -func (s *MorphBalanceContract) SetBalanceContractClient(v StaticContractClient) { - s.balanceContract = v -} - -// SetBalanceOfMethodName is a Balance contract balanceOf method name setter. -func (s *MorphBalanceContract) SetBalanceOfMethodName(v string) { - s.balanceOfMethodName = v -} - -// SetDecimalsMethodName is a Balance contract decimals method name setter. -func (s *MorphBalanceContract) SetDecimalsMethodName(v string) { - s.decimalsMethodName = v -} - -// BalanceOf performs the test invocation call of balanceOf method of NeoFS Balance contract. -func (s MorphBalanceContract) BalanceOf(p BalanceOfParams) (*BalanceOfResult, error) { - owner := p.OwnerID() - - u160, err := address.StringToUint160(owner.String()) - if err != nil { - return nil, errors.Wrap(err, "could not convert wallet address to Uint160") - } - - prms, err := s.balanceContract.TestInvoke( - s.balanceOfMethodName, - u160.BytesBE(), - ) - if err != nil { - return nil, errors.Wrap(err, "could not perform test invocation") - } else if ln := len(prms); ln != 1 { - return nil, errors.Errorf("unexpected stack item count (balanceOf): %d", ln) - } - - amount, err := goclient.IntFromStackParameter(prms[0]) - if err != nil { - return nil, errors.Wrap(err, "could not get integer stack item from stack item (amount)") - } - - res := new(BalanceOfResult) - res.SetAmount(amount) - - return res, nil -} - -// Decimals performs the test invocation call of decimals method of NeoFS Balance contract. -func (s MorphBalanceContract) Decimals(DecimalsParams) (*DecimalsResult, error) { - prms, err := s.balanceContract.TestInvoke( - s.decimalsMethodName, - ) - if err != nil { - return nil, errors.Wrap(err, "could not perform test invocation") - } else if ln := len(prms); ln != 1 { - return nil, errors.Errorf("unexpected stack item count (decimals): %d", ln) - } - - dec, err := goclient.IntFromStackParameter(prms[0]) - if err != nil { - return nil, errors.Wrap(err, "could not get integer stack item from stack item (decimal)") - } - - res := new(DecimalsResult) - res.SetDecimals(dec) - - return res, nil -} - -// SetOwnerID is an owner ID setter. -func (s *BalanceOfParams) SetOwnerID(v refs.OwnerID) { - s.owner = v -} - -// OwnerID is an owner ID getter. -func (s BalanceOfParams) OwnerID() refs.OwnerID { - return s.owner -} - -// SetAmount is an funds amount setter. -func (s *BalanceOfResult) SetAmount(v int64) { - s.amount = v -} - -// Amount is an funds amount getter. -func (s BalanceOfResult) Amount() int64 { - return s.amount -} - -// SetDecimals is a decimals setter. -func (s *DecimalsResult) SetDecimals(v int64) { - s.dec = v -} - -// Decimals is a decimals getter. -func (s DecimalsResult) Decimals() int64 { - return s.dec -} diff --git a/lib/implementations/balance_test.go b/lib/implementations/balance_test.go deleted file mode 100644 index c9b571c8a..000000000 --- a/lib/implementations/balance_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package implementations - -import ( - "testing" - - "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/stretchr/testify/require" -) - -func TestBalanceOfParams(t *testing.T) { - s := BalanceOfParams{} - - owner := refs.OwnerID{1, 2, 3} - s.SetOwnerID(owner) - - require.Equal(t, owner, s.OwnerID()) -} - -func TestBalanceOfResult(t *testing.T) { - s := BalanceOfResult{} - - amount := int64(100) - s.SetAmount(amount) - - require.Equal(t, amount, s.Amount()) -} - -func TestDecimalsResult(t *testing.T) { - s := DecimalsResult{} - - dec := int64(100) - s.SetDecimals(dec) - - require.Equal(t, dec, s.Decimals()) -} diff --git a/lib/implementations/bootstrap.go b/lib/implementations/bootstrap.go deleted file mode 100644 index 458967521..000000000 --- a/lib/implementations/bootstrap.go +++ /dev/null @@ -1,311 +0,0 @@ -package implementations - -import ( - "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neofs-api-go/bootstrap" - "github.com/nspcc-dev/neofs-node/lib/blockchain/goclient" - "github.com/nspcc-dev/neofs-node/lib/boot" - "github.com/nspcc-dev/neofs-node/lib/ir" - "github.com/nspcc-dev/neofs-node/lib/netmap" - "github.com/pkg/errors" -) - -// MorphNetmapContract is a wrapper over NeoFS Netmap contract client -// that provides an interface of network map manipulations. -type MorphNetmapContract struct { - // NeoFS Netmap smart-contract - netmapContract StaticContractClient - - // add peer method name of netmap contract - addPeerMethodName string - - // new epoch method name of netmap contract - newEpochMethodName string - - // get netmap method name of netmap contract - getNetMapMethodName string - - // update state method name of netmap contract - updStateMethodName string - - // IR list method name of netmap contract - irListMethodName string -} - -// UpdateEpochParams is a structure that groups the parameters -// for NeoFS epoch number updating. -type UpdateEpochParams struct { - epoch uint64 -} - -// UpdateStateParams is a structure that groups the parameters -// for NeoFS node state updating. -type UpdateStateParams struct { - st NodeState - - key []byte -} - -// NodeState is a type of node states enumeration. -type NodeState int64 - -const ( - _ NodeState = iota - - // StateOffline is an offline node state value. - StateOffline -) - -const addPeerFixedArgNumber = 2 - -const nodeInfoFixedPrmNumber = 3 - -// SetNetmapContractClient is a Netmap contract client setter. -func (s *MorphNetmapContract) SetNetmapContractClient(v StaticContractClient) { - s.netmapContract = v -} - -// SetAddPeerMethodName is a Netmap contract AddPeer method name setter. -func (s *MorphNetmapContract) SetAddPeerMethodName(v string) { - s.addPeerMethodName = v -} - -// SetNewEpochMethodName is a Netmap contract NewEpoch method name setter. -func (s *MorphNetmapContract) SetNewEpochMethodName(v string) { - s.newEpochMethodName = v -} - -// SetNetMapMethodName is a Netmap contract Netmap method name setter. -func (s *MorphNetmapContract) SetNetMapMethodName(v string) { - s.getNetMapMethodName = v -} - -// SetUpdateStateMethodName is a Netmap contract UpdateState method name setter. -func (s *MorphNetmapContract) SetUpdateStateMethodName(v string) { - s.updStateMethodName = v -} - -// SetIRListMethodName is a Netmap contract InnerRingList method name setter. -func (s *MorphNetmapContract) SetIRListMethodName(v string) { - s.irListMethodName = v -} - -// AddPeer invokes the call of AddPeer method of NeoFS Netmap contract. -func (s *MorphNetmapContract) AddPeer(p boot.BootstrapPeerParams) error { - info := p.NodeInfo() - opts := info.GetOptions() - - args := make([]interface{}, 0, addPeerFixedArgNumber+len(opts)) - - args = append(args, - // Address - []byte(info.GetAddress()), - - // Public key - info.GetPubKey(), - ) - - // Options - for i := range opts { - args = append(args, []byte(opts[i])) - } - - return s.netmapContract.Invoke( - s.addPeerMethodName, - args..., - ) -} - -// UpdateEpoch invokes the call of NewEpoch method of NeoFS Netmap contract. -func (s *MorphNetmapContract) UpdateEpoch(p UpdateEpochParams) error { - return s.netmapContract.Invoke( - s.newEpochMethodName, - int64(p.Number()), // TODO: do not cast after uint64 type will become supported in client - ) -} - -// GetNetMap performs the test invocation call of Netmap method of NeoFS Netmap contract. -func (s *MorphNetmapContract) GetNetMap(p netmap.GetParams) (*netmap.GetResult, error) { - prms, err := s.netmapContract.TestInvoke( - s.getNetMapMethodName, - ) - if err != nil { - return nil, errors.Wrap(err, "could not perform test invocation") - } else if ln := len(prms); ln != 1 { - return nil, errors.Errorf("unexpected stack item count (Nodes): %d", ln) - } - - prms, err = goclient.ArrayFromStackParameter(prms[0]) - if err != nil { - return nil, errors.Wrap(err, "could not get stack item array from stack item (Nodes)") - } - - nm := netmap.NewNetmap() - - for i := range prms { - nodeInfo, err := nodeInfoFromStackItem(prms[i]) - if err != nil { - return nil, errors.Wrapf(err, "could not parse stack item (Node #%d)", i) - } - - if err := nm.AddNode(nodeInfo); err != nil { - return nil, errors.Wrapf(err, "could not add node #%d to network map", i) - } - } - - res := new(netmap.GetResult) - res.SetNetMap(nm) - - return res, nil -} - -func nodeInfoFromStackItem(prm smartcontract.Parameter) (*bootstrap.NodeInfo, error) { - prms, err := goclient.ArrayFromStackParameter(prm) - if err != nil { - return nil, errors.Wrapf(err, "could not get stack item array (NodeInfo)") - } else if ln := len(prms); ln != nodeInfoFixedPrmNumber { - return nil, errors.Errorf("unexpected stack item count (NodeInfo): expected %d, has %d", 3, ln) - } - - res := new(bootstrap.NodeInfo) - - // Address - addrBytes, err := goclient.BytesFromStackParameter(prms[0]) - if err != nil { - return nil, errors.Wrap(err, "could not get byte array from stack item (Address)") - } - - res.Address = string(addrBytes) - - // Public key - res.PubKey, err = goclient.BytesFromStackParameter(prms[1]) - if err != nil { - return nil, errors.Wrap(err, "could not get byte array from stack item (Public key)") - } - - // Options - prms, err = goclient.ArrayFromStackParameter(prms[2]) - if err != nil { - return nil, errors.Wrapf(err, "could not get stack item array (Options)") - } - - res.Options = make([]string, 0, len(prms)) - - for i := range prms { - optBytes, err := goclient.BytesFromStackParameter(prms[i]) - if err != nil { - return nil, errors.Wrapf(err, "could not get byte array from stack item (Option #%d)", i) - } - - res.Options = append(res.Options, string(optBytes)) - } - - return res, nil -} - -// UpdateState invokes the call of UpdateState method of NeoFS Netmap contract. -func (s *MorphNetmapContract) UpdateState(p UpdateStateParams) error { - return s.netmapContract.Invoke( - s.updStateMethodName, - p.State().Int64(), - p.Key(), - ) -} - -// GetIRInfo performs the test invocation call of InnerRingList method of NeoFS Netmap contract. -func (s *MorphNetmapContract) GetIRInfo(ir.GetInfoParams) (*ir.GetInfoResult, error) { - prms, err := s.netmapContract.TestInvoke( - s.irListMethodName, - ) - if err != nil { - return nil, errors.Wrap(err, "could not perform test invocation") - } else if ln := len(prms); ln != 1 { - return nil, errors.Errorf("unexpected stack item count (Nodes): %d", ln) - } - - irInfo, err := irInfoFromStackItem(prms[0]) - if err != nil { - return nil, errors.Wrap(err, "could not get IR info from stack item") - } - - res := new(ir.GetInfoResult) - res.SetInfo(*irInfo) - - return res, nil -} - -func irInfoFromStackItem(prm smartcontract.Parameter) (*ir.Info, error) { - prms, err := goclient.ArrayFromStackParameter(prm) - if err != nil { - return nil, errors.Wrap(err, "could not get stack item array") - } - - nodes := make([]ir.Node, 0, len(prms)) - - for i := range prms { - node, err := irNodeFromStackItem(prms[i]) - if err != nil { - return nil, errors.Wrapf(err, "could not get node info from stack item (IRNode #%d)", i) - } - - nodes = append(nodes, *node) - } - - info := new(ir.Info) - info.SetNodes(nodes) - - return info, nil -} - -func irNodeFromStackItem(prm smartcontract.Parameter) (*ir.Node, error) { - prms, err := goclient.ArrayFromStackParameter(prm) - if err != nil { - return nil, errors.Wrap(err, "could not get stack item array (IRNode)") - } - - // Public key - keyBytes, err := goclient.BytesFromStackParameter(prms[0]) - if err != nil { - return nil, errors.Wrap(err, "could not get byte array from stack item (Key)") - } - - node := new(ir.Node) - node.SetKey(keyBytes) - - return node, nil -} - -// SetNumber is an epoch number setter. -func (s *UpdateEpochParams) SetNumber(v uint64) { - s.epoch = v -} - -// Number is an epoch number getter. -func (s UpdateEpochParams) Number() uint64 { - return s.epoch -} - -// SetState is a state setter. -func (s *UpdateStateParams) SetState(v NodeState) { - s.st = v -} - -// State is a state getter. -func (s UpdateStateParams) State() NodeState { - return s.st -} - -// SetKey is a public key setter. -func (s *UpdateStateParams) SetKey(v []byte) { - s.key = v -} - -// Key is a public key getter. -func (s UpdateStateParams) Key() []byte { - return s.key -} - -// Int64 converts NodeState to int64. -func (s NodeState) Int64() int64 { - return int64(s) -} diff --git a/lib/implementations/bootstrap_test.go b/lib/implementations/bootstrap_test.go deleted file mode 100644 index a9968ae98..000000000 --- a/lib/implementations/bootstrap_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package implementations - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestUpdateEpochParams(t *testing.T) { - s := UpdateEpochParams{} - - e := uint64(100) - s.SetNumber(e) - - require.Equal(t, e, s.Number()) -} - -func TestUpdateStateParams(t *testing.T) { - s := UpdateStateParams{} - - st := NodeState(1) - s.SetState(st) - - require.Equal(t, st, s.State()) - - key := []byte{1, 2, 3} - s.SetKey(key) - - require.Equal(t, key, s.Key()) -} diff --git a/lib/implementations/epoch.go b/lib/implementations/epoch.go deleted file mode 100644 index 16d9a5c37..000000000 --- a/lib/implementations/epoch.go +++ /dev/null @@ -1,7 +0,0 @@ -package implementations - -// EpochReceiver is an interface of the container -// of NeoFS epoch number with read access. -type EpochReceiver interface { - Epoch() uint64 -} diff --git a/lib/implementations/reputation.go b/lib/implementations/reputation.go deleted file mode 100644 index 2fb4865e2..000000000 --- a/lib/implementations/reputation.go +++ /dev/null @@ -1,41 +0,0 @@ -package implementations - -import ( - "github.com/nspcc-dev/neofs-node/lib/peers" -) - -// MorphReputationContract is a wrapper over NeoFS Reputation contract client -// that provides an interface of the storage of global trust values. -type MorphReputationContract struct { - // NeoFS Reputation smart-contract - repContract StaticContractClient - - // put method name of reputation contract - putMethodName string - - // list method name of reputation contract - listMethodName string - - // public key storage - pkStore peers.PublicKeyStore -} - -// SetReputationContractClient is a Reputation contract client setter. -func (s *MorphReputationContract) SetReputationContractClient(v StaticContractClient) { - s.repContract = v -} - -// SetPublicKeyStore is a public key store setter. -func (s *MorphReputationContract) SetPublicKeyStore(v peers.PublicKeyStore) { - s.pkStore = v -} - -// SetPutMethodName is a Reputation contract Put method name setter. -func (s *MorphReputationContract) SetPutMethodName(v string) { - s.putMethodName = v -} - -// SetListMethodName is a Reputation contract List method name setter. -func (s *MorphReputationContract) SetListMethodName(v string) { - s.listMethodName = v -} diff --git a/lib/ir/info.go b/lib/ir/info.go deleted file mode 100644 index 991a1efad..000000000 --- a/lib/ir/info.go +++ /dev/null @@ -1,17 +0,0 @@ -package ir - -// Info is a structure that groups the information -// about inner ring. -type Info struct { - nodes []Node -} - -// SetNodes is an IR node list setter. -func (s *Info) SetNodes(v []Node) { - s.nodes = v -} - -// Nodes is an IR node list getter. -func (s Info) Nodes() []Node { - return s.nodes -} diff --git a/lib/ir/info_test.go b/lib/ir/info_test.go deleted file mode 100644 index 6b1f3df4b..000000000 --- a/lib/ir/info_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package ir - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestInfo(t *testing.T) { - s := Info{} - - n1 := Node{} - n1.SetKey([]byte{1, 2, 3}) - - n2 := Node{} - n2.SetKey([]byte{4, 5, 6}) - - nodes := []Node{ - n1, - n2, - } - s.SetNodes(nodes) - - require.Equal(t, nodes, s.Nodes()) -} diff --git a/lib/ir/node.go b/lib/ir/node.go deleted file mode 100644 index c1a765b5d..000000000 --- a/lib/ir/node.go +++ /dev/null @@ -1,17 +0,0 @@ -package ir - -// Node is a structure that groups -// the information about IR node. -type Node struct { - key []byte -} - -// SetKey is an IR node public key setter. -func (s *Node) SetKey(v []byte) { - s.key = v -} - -// Key is an IR node public key getter. -func (s Node) Key() []byte { - return s.key -} diff --git a/lib/ir/node_test.go b/lib/ir/node_test.go deleted file mode 100644 index 9663caf9c..000000000 --- a/lib/ir/node_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package ir - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNode(t *testing.T) { - s := Node{} - - key := []byte{1, 2, 3} - s.SetKey(key) - - require.Equal(t, key, s.Key()) -} diff --git a/lib/ir/storage.go b/lib/ir/storage.go deleted file mode 100644 index 8df21933d..000000000 --- a/lib/ir/storage.go +++ /dev/null @@ -1,94 +0,0 @@ -package ir - -import ( - "bytes" - - crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/pkg/errors" -) - -// Storage is an interface of the storage of info about NeoFS IR. -type Storage interface { - GetIRInfo(GetInfoParams) (*GetInfoResult, error) -} - -// GetInfoParams is a structure that groups the parameters -// for IR info receiving operation. -type GetInfoParams struct { -} - -// GetInfoResult is a structure that groups -// values returned by IR info receiving operation. -type GetInfoResult struct { - info Info -} - -// ErrNilStorage is returned by functions that expect -// a non-nil Storage, but received nil. -const ErrNilStorage = internal.Error("inner ring storage is nil") - -// SetInfo is an IR info setter. -func (s *GetInfoResult) SetInfo(v Info) { - s.info = v -} - -// Info is an IR info getter. -func (s GetInfoResult) Info() Info { - return s.info -} - -// BinaryKeyList returns the list of binary public key of IR nodes. -// -// If passed Storage is nil, ErrNilStorage returns. -func BinaryKeyList(storage Storage) ([][]byte, error) { - if storage == nil { - return nil, ErrNilStorage - } - - // get IR info - getRes, err := storage.GetIRInfo(GetInfoParams{}) - if err != nil { - return nil, errors.Wrap( - err, - "could not get information about IR", - ) - } - - nodes := getRes.Info().Nodes() - - keys := make([][]byte, 0, len(nodes)) - - for i := range nodes { - keys = append(keys, nodes[i].Key()) - } - - return keys, nil -} - -// IsInnerRingKey checks if the passed argument is the -// key of one of IR nodes. -// -// Uses BinaryKeyList function to receive the key list of IR nodes internally. -// -// If passed key slice is empty, crypto.ErrEmptyPublicKey returns immediately. -func IsInnerRingKey(storage Storage, key []byte) (bool, error) { - // check key emptiness - // TODO: summarize the void check to a full IR key-format check. - if len(key) == 0 { - return false, crypto.ErrEmptyPublicKey - } - - irKeys, err := BinaryKeyList(storage) - if err != nil { - return false, err - } - - for i := range irKeys { - if bytes.Equal(irKeys[i], key) { - return true, nil - } - } - - return false, nil -} diff --git a/lib/ir/storage_test.go b/lib/ir/storage_test.go deleted file mode 100644 index 71a654847..000000000 --- a/lib/ir/storage_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package ir - -import ( - "testing" - - crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" -) - -type testInfoReceiver struct { - keys [][]byte - - err error -} - -func (s testInfoReceiver) GetIRInfo(GetInfoParams) (*GetInfoResult, error) { - if s.err != nil { - return nil, s.err - } - - nodes := make([]Node, 0, len(s.keys)) - - for i := range s.keys { - node := Node{} - node.SetKey(s.keys[i]) - - nodes = append(nodes, node) - } - - info := Info{} - info.SetNodes(nodes) - - res := new(GetInfoResult) - res.SetInfo(info) - - return res, nil -} - -func (s *testInfoReceiver) addKey(key []byte) { - s.keys = append(s.keys, key) -} - -func TestGetInfoResult(t *testing.T) { - s := GetInfoResult{} - - info := Info{} - - n := Node{} - n.SetKey([]byte{1, 2, 3}) - - info.SetNodes([]Node{ - n, - }) - - s.SetInfo(info) - - require.Equal(t, info, s.Info()) -} - -func TestIsInnerRingKey(t *testing.T) { - var ( - res bool - err error - s = new(testInfoReceiver) - ) - - // empty public key - res, err = IsInnerRingKey(nil, nil) - require.EqualError(t, err, crypto.ErrEmptyPublicKey.Error()) - - key := []byte{1, 2, 3} - - // nil Storage - res, err = IsInnerRingKey(nil, key) - require.EqualError(t, err, ErrNilStorage.Error()) - - // force Storage to return an error - s.err = errors.New("some error") - - // Storage error - res, err = IsInnerRingKey(s, key) - require.EqualError(t, errors.Cause(err), s.err.Error()) - - // reset Storage error - s.err = nil - - // IR keys don't contain key - s.addKey(append(key, 1)) - - res, err = IsInnerRingKey(s, key) - require.NoError(t, err) - require.False(t, res) - - // IR keys contain key - s.addKey(key) - - res, err = IsInnerRingKey(s, key) - require.NoError(t, err) - require.True(t, res) -} diff --git a/lib/netmap/netmap.go b/lib/netmap/netmap.go deleted file mode 100644 index e339d0f9b..000000000 --- a/lib/netmap/netmap.go +++ /dev/null @@ -1,392 +0,0 @@ -package netmap - -import ( - "crypto/sha256" - "encoding/json" - "reflect" - "sort" - "sync" - - "github.com/nspcc-dev/neofs-api-go/bootstrap" - "github.com/nspcc-dev/netmap" - "github.com/pkg/errors" - "github.com/spaolacci/murmur3" -) - -type ( - // Bucket is an alias for github.com/nspcc-dev/netmap.Bucket - Bucket = netmap.Bucket - // SFGroup is an alias for github.com/nspcc-dev/netmap.SFGroup - SFGroup = netmap.SFGroup - // Select is an alias for github.com/nspcc-dev/netmap.Select - Select = netmap.Select - // Filter is an alias for github.com/nspcc-dev/netmap.Filter - Filter = netmap.Filter - // SimpleFilter is an alias for github.com/nspcc-dev/netmap.Filter - SimpleFilter = netmap.SimpleFilter - // PlacementRule is an alias for github.com/nspcc-dev/netmap.Filter - PlacementRule = netmap.PlacementRule - - // NetMap is a general network map structure for NeoFS - NetMap struct { - mu *sync.RWMutex - root Bucket - items Nodes - } - - // Nodes is an alias for slice of NodeInfo which is structure that describes every host - Nodes []bootstrap.NodeInfo -) - -const ( - // Separator separates key:value pairs in string representation of options. - Separator = netmap.Separator - - // NodesBucket is the name for optionless bucket containing only nodes. - NodesBucket = netmap.NodesBucket -) - -var ( - // FilterIn returns filter, which checks if value is in specified list. - FilterIn = netmap.FilterIn - // FilterNotIn returns filter, which checks if value is not in specified list. - FilterNotIn = netmap.FilterNotIn - // FilterOR returns OR combination of filters. - FilterOR = netmap.FilterOR - // FilterAND returns AND combination of filters. - FilterAND = netmap.FilterAND - // FilterEQ returns filter, which checks if value is equal to v. - FilterEQ = netmap.FilterEQ - // FilterNE returns filter, which checks if value is not equal to v. - FilterNE = netmap.FilterNE - // FilterGT returns filter, which checks if value is greater than v. - FilterGT = netmap.FilterGT - // FilterGE returns filter, which checks if value is greater or equal than v. - FilterGE = netmap.FilterGE - // FilterLT returns filter, which checks if value is less than v. - FilterLT = netmap.FilterLT - // FilterLE returns filter, which checks if value is less or equal than v. - FilterLE = netmap.FilterLE -) - -var errNetMapsConflict = errors.New("netmaps are in conflict") - -// Copy creates new slice of copied nodes. -func (n Nodes) Copy() Nodes { - res := make(Nodes, len(n)) - for i := range n { - res[i].Address = n[i].Address - res[i].Status = n[i].Status - - if n[i].PubKey != nil { - res[i].PubKey = make([]byte, len(n[i].PubKey)) - copy(res[i].PubKey, n[i].PubKey) - } - - if n[i].Options != nil { - res[i].Options = make([]string, len(n[i].Options)) - copy(res[i].Options, n[i].Options) - } - } - - return res -} - -// NewNetmap is an constructor. -func NewNetmap() *NetMap { - return &NetMap{ - items: make([]bootstrap.NodeInfo, 0), - mu: new(sync.RWMutex), - } -} - -// Equals return whether two netmap are identical. -func (n *NetMap) Equals(nm *NetMap) bool { - n.mu.RLock() - defer n.mu.RUnlock() - - return len(n.items) == len(nm.items) && - n.root.Equals(nm.root) && - reflect.DeepEqual(n.items, nm.items) -} - -// Root returns netmap root-bucket. -func (n *NetMap) Root() *Bucket { - n.mu.RLock() - cp := n.root.Copy() - n.mu.RUnlock() - - return &cp -} - -// Copy creates and returns full copy of target netmap. -func (n *NetMap) Copy() *NetMap { - n.mu.RLock() - defer n.mu.RUnlock() - - nm := NewNetmap() - nm.items = n.items.Copy() - nm.root = n.root.Copy() - - return nm -} - -type hashedItem struct { - h uint32 - info *bootstrap.NodeInfo -} - -// Normalise reorders netmap items into some canonical order. -func (n *NetMap) Normalise() *NetMap { - nm := NewNetmap() - items := n.items.Copy() - - if len(items) == 0 { - return nm - } - - itemsH := make([]hashedItem, len(n.items)) - for i := range itemsH { - itemsH[i].h = murmur3.Sum32(n.items[i].PubKey) - itemsH[i].info = &items[i] - } - - sort.Slice(itemsH, func(i, j int) bool { - if itemsH[i].h == itemsH[j].h { - return itemsH[i].info.Address < itemsH[j].info.Address - } - return itemsH[i].h < itemsH[j].h - }) - - lastHash := ^itemsH[0].h - lastAddr := "" - - for i := range itemsH { - if itemsH[i].h != lastHash || itemsH[i].info.Address != lastAddr { - _ = nm.AddNode(itemsH[i].info) - lastHash = itemsH[i].h - } - } - - return nm -} - -// Hash returns hash of n. -func (n *NetMap) Hash() (sum [32]byte) { - items := n.Normalise().Items() - w := sha256.New() - - for i := range items { - data, _ := items[i].Marshal() - _, _ = w.Write(data) - } - - s := w.Sum(nil) - copy(sum[:], s) - - return -} - -// InheritWeights calculates average capacity and minimal price, then provides buckets with IQR weight. -func (n *NetMap) InheritWeights() *NetMap { - nm := n.Copy() - - // find average capacity in the network map - meanCap := nm.root.Traverse(netmap.NewMeanAgg(), netmap.CapWeightFunc).Compute() - capNorm := netmap.NewSigmoidNorm(meanCap) - - // find minimal price in the network map - minPrice := nm.root.Traverse(netmap.NewMinAgg(), netmap.PriceWeightFunc).Compute() - priceNorm := netmap.NewReverseMinNorm(minPrice) - - // provide all buckets with - wf := netmap.NewWeightFunc(capNorm, priceNorm) - meanAF := netmap.AggregatorFactory{New: netmap.NewMeanIQRAgg} - nm.root.TraverseTree(meanAF, wf) - - return nm -} - -// Merge checks if merge is possible and then add new elements from given netmap. -func (n *NetMap) Merge(n1 *NetMap) error { - n.mu.Lock() - defer n.mu.Unlock() - - var ( - tr = make(map[uint32]netmap.Node, len(n1.items)) - items = n.items - ) - -loop: - for j := range n1.items { - for i := range n.items { - if n.items[i].Equals(n1.items[j]) { - tr[uint32(j)] = netmap.Node{ - N: uint32(i), - C: n.items[i].Capacity(), - P: n.items[i].Price(), - } - continue loop - } - } - tr[uint32(j)] = netmap.Node{ - N: uint32(len(items)), - C: n1.items[j].Capacity(), - P: n1.items[j].Price(), - } - items = append(items, n1.items[j]) - } - - root := n1.root.UpdateIndices(tr) - if n.root.CheckConflicts(root) { - return errNetMapsConflict - } - - n.items = items - n.root.Merge(root) - - return nil -} - -// FindGraph finds sub-graph filtered by given SFGroup. -func (n *NetMap) FindGraph(pivot []byte, ss ...SFGroup) (c *Bucket) { - n.mu.RLock() - defer n.mu.RUnlock() - - return n.root.FindGraph(pivot, ss...) -} - -// FindNodes finds sub-graph filtered by given SFGroup and returns all sub-graph items. -func (n *NetMap) FindNodes(pivot []byte, ss ...SFGroup) (nodes []uint32) { - n.mu.RLock() - defer n.mu.RUnlock() - - return n.root.FindNodes(pivot, ss...).Nodes() -} - -// Items return slice of all NodeInfo in netmap. -func (n *NetMap) Items() []bootstrap.NodeInfo { - n.mu.RLock() - defer n.mu.RUnlock() - - return n.items -} - -// ItemsCopy return copied slice of all NodeInfo in netmap (is it useful?). -func (n *NetMap) ItemsCopy() Nodes { - n.mu.RLock() - defer n.mu.RUnlock() - - return n.items.Copy() -} - -// Add adds node with given address and given options. -func (n *NetMap) Add(addr string, pk []byte, st bootstrap.NodeStatus, opts ...string) error { - return n.AddNode(&bootstrap.NodeInfo{Address: addr, PubKey: pk, Status: st, Options: opts}) -} - -// Update replaces netmap with given netmap. -func (n *NetMap) Update(nxt *NetMap) { - n.mu.Lock() - defer n.mu.Unlock() - - n.root = nxt.root - n.items = nxt.items -} - -// GetMaxSelection returns 'maximal container' -- subgraph which contains -// any other subgraph satisfying specified selects and filters. -func (n *NetMap) GetMaxSelection(ss []Select, fs []Filter) (r *Bucket) { - return n.root.GetMaxSelection(netmap.SFGroup{Selectors: ss, Filters: fs}) -} - -// AddNode adds to exited or new node slice of given options. -func (n *NetMap) AddNode(nodeInfo *bootstrap.NodeInfo, opts ...string) error { - n.mu.Lock() - defer n.mu.Unlock() - - info := *nodeInfo - - info.Options = append(info.Options, opts...) - - num := -1 - - // looking for existed node info item - for i := range n.items { - if n.items[i].Equals(info) { - num = i - break - } - } - // if item is not existed - add it - if num < 0 { - num = len(n.items) - n.items = append(n.items, info) - } - - return n.root.AddStrawNode(netmap.Node{ - N: uint32(num), - C: n.items[num].Capacity(), - P: n.items[num].Price(), - }, info.Options...) -} - -// GetNodesByOption returns slice of NodeInfo that has given option. -func (n *NetMap) GetNodesByOption(opts ...string) []bootstrap.NodeInfo { - n.mu.RLock() - defer n.mu.RUnlock() - - ns := n.root.GetNodesByOption(opts...) - nodes := make([]bootstrap.NodeInfo, 0, len(ns)) - - for _, info := range ns { - nodes = append(nodes, n.items[info.N]) - } - - return nodes -} - -// MarshalJSON custom marshaller. -func (n *NetMap) MarshalJSON() ([]byte, error) { - n.mu.RLock() - defer n.mu.RUnlock() - - return json.Marshal(n.items) -} - -// UnmarshalJSON custom unmarshaller. -func (n *NetMap) UnmarshalJSON(data []byte) error { - var ( - nm = NewNetmap() - items []bootstrap.NodeInfo - ) - - if err := json.Unmarshal(data, &items); err != nil { - return err - } - - for i := range items { - if err := nm.Add(items[i].Address, items[i].PubKey, items[i].Status, items[i].Options...); err != nil { - return err - } - } - - if n.mu == nil { - n.mu = new(sync.RWMutex) - } - - n.mu.Lock() - n.root = nm.root - n.items = nm.items - n.mu.Unlock() - - return nil -} - -// Size returns number of nodes in network map. -func (n *NetMap) Size() int { - n.mu.RLock() - defer n.mu.RUnlock() - - return len(n.items) -} diff --git a/lib/netmap/netmap_test.go b/lib/netmap/netmap_test.go deleted file mode 100644 index 1cd579b61..000000000 --- a/lib/netmap/netmap_test.go +++ /dev/null @@ -1,261 +0,0 @@ -package netmap - -import ( - "bytes" - "encoding/json" - "math/rand" - "sync" - "testing" - "time" - - "github.com/nspcc-dev/neofs-api-go/bootstrap" - "github.com/nspcc-dev/neofs-api-go/object" - "github.com/nspcc-dev/netmap" - "github.com/stretchr/testify/require" -) - -func TestNetMap_DataRace(t *testing.T) { - var ( - nm = NewNetmap() - wg = new(sync.WaitGroup) - nodes = []bootstrap.NodeInfo{ - {Address: "SPB1", Options: []string{"/Location:Europe/Country:USA"}}, - {Address: "SPB2", Options: []string{"/Location:Europe/Country:Italy"}}, - {Address: "MSK1", Options: []string{"/Location:Europe/Country:Germany"}}, - {Address: "MSK2", Options: []string{"/Location:Europe/Country:Russia"}}, - } - ) - - wg.Add(10) - for i := 0; i < 10; i++ { - go func(n int) { - for _, node := range nodes { - require.NoError(t, nm.Add(node.Address, node.PubKey, 0, node.Options...)) - // t.Logf("%02d: add node %q", n, node.Address) - } - - wg.Done() - }(i) - } - - wg.Add(3 * 10) - for i := 0; i < 10; i++ { - go func(n int) { - nm.Copy() - // t.Logf("%02d: Copy", n) - wg.Done() - }(i) - go func(n int) { - nm.Items() - // t.Logf("%02d: Items", n) - wg.Done() - }(i) - go func(n int) { - nm.Root() - // t.Logf("%02d: Root", n) - wg.Done() - }(i) - } - - wg.Wait() -} - -func TestNetMapSuite(t *testing.T) { - var ( - err error - nm1 = NewNetmap() - nodes = []bootstrap.NodeInfo{ - {Address: "SPB1", Options: []string{"/Location:Europe/Country:USA"}, Status: 1}, - {Address: "SPB2", Options: []string{"/Location:Europe/Country:Italy"}, Status: 2}, - {Address: "MSK1", Options: []string{"/Location:Europe/Country:Germany"}, Status: 3}, - {Address: "MSK2", Options: []string{"/Location:Europe/Country:Russia"}, Status: 4}, - } - ) - - for _, node := range nodes { - err = nm1.Add(node.Address, nil, node.Status, node.Options...) - require.NoError(t, err) - } - - t.Run("copy should work like expected", func(t *testing.T) { - nm2 := nm1.Copy() - require.Equal(t, nm1.root, nm2.root) - require.Equal(t, nm1.items, nm2.items) - }) - - t.Run("add node should not ignore options", func(t *testing.T) { - items := nm1.ItemsCopy() - - nm2 := NewNetmap() - err = nm2.AddNode(&items[0], "/New/Option") - require.NoError(t, err) - require.Len(t, nm2.items, 1) - require.Equal(t, append(items[0].Options, "/New/Option"), nm2.items[0].Options) - }) - - t.Run("copyItems should work like expected", func(t *testing.T) { - require.Equal(t, nm1.items, nm1.ItemsCopy()) - }) - - t.Run("marshal / unmarshal should be identical on same data", func(t *testing.T) { - var nm2 *NetMap - want, err := json.Marshal(nodes) - require.NoError(t, err) - - actual, err := json.Marshal(nm1) - require.NoError(t, err) - - require.Equal(t, want, actual) - - err = json.Unmarshal(actual, &nm2) - require.NoError(t, err) - require.Equal(t, nm1.root, nm2.root) - require.Equal(t, nm1.items, nm2.items) - }) - - t.Run("unmarshal should override existing data", func(t *testing.T) { - var nm2 *NetMap - - want, err := json.Marshal(nodes) - require.NoError(t, err) - - actual, err := json.Marshal(nm1) - require.NoError(t, err) - - require.Equal(t, want, actual) - - nm2 = nm1.Copy() - err = nm2.Add("SOMEADDR", nil, 0, "/Location:Europe/Country:USA") - require.NoError(t, err) - - err = json.Unmarshal(actual, &nm2) - require.NoError(t, err) - require.Equal(t, nm1.root, nm2.root) - require.Equal(t, nm1.items, nm2.items) - }) - - t.Run("unmarshal should fail on bad data", func(t *testing.T) { - var nm2 *NetMap - require.Error(t, json.Unmarshal([]byte(`"some bad data"`), &nm2)) - }) - - t.Run("unmarshal should fail on add nodes", func(t *testing.T) { - var nm2 *NetMap - require.Error(t, json.Unmarshal([]byte(`[{"address": "SPB1","options":["1-2-3-4"]}]`), &nm2)) - }) - - t.Run("merge two netmaps", func(t *testing.T) { - newNodes := []bootstrap.NodeInfo{ - {Address: "SPB3", Options: []string{"/Location:Europe/Country:France"}}, - } - nm2 := NewNetmap() - for _, node := range newNodes { - err = nm2.Add(node.Address, nil, 0, node.Options...) - require.NoError(t, err) - } - - err = nm2.Merge(nm1) - require.NoError(t, err) - require.Len(t, nm2.items, len(nodes)+len(newNodes)) - - ns := nm2.FindNodes([]byte("pivot"), netmap.SFGroup{ - Filters: []Filter{{Key: "Country", F: FilterEQ("Germany")}}, - Selectors: []Select{{Count: 1, Key: NodesBucket}}, - }) - require.Len(t, ns, 1) - }) - - t.Run("weighted netmaps", func(t *testing.T) { - strawNodes := []bootstrap.NodeInfo{ - {Address: "SPB2", Options: []string{"/Location:Europe/Country:Italy", "/Capacity:10", "/Price:100"}}, - {Address: "MSK1", Options: []string{"/Location:Europe/Country:Germany", "/Capacity:10", "/Price:1"}}, - {Address: "MSK2", Options: []string{"/Location:Europe/Country:Russia", "/Capacity:5", "/Price:10"}}, - {Address: "SPB1", Options: []string{"/Location:Europe/Country:France", "/Capacity:20", "/Price:2"}}, - } - nm2 := NewNetmap() - for _, node := range strawNodes { - err = nm2.Add(node.Address, nil, 0, node.Options...) - require.NoError(t, err) - } - - ns1 := nm1.FindNodes([]byte("pivot"), netmap.SFGroup{ - Selectors: []Select{{Count: 2, Key: NodesBucket}}, - }) - require.Len(t, ns1, 2) - - ns2 := nm2.FindNodes([]byte("pivot"), netmap.SFGroup{ - Selectors: []Select{{Count: 2, Key: NodesBucket}}, - }) - require.Len(t, ns2, 2) - require.NotEqual(t, ns1, ns2) - require.Equal(t, []uint32{1, 3}, ns2) - }) -} - -func TestNetMap_Normalise(t *testing.T) { - const testCount = 5 - - nodes := []bootstrap.NodeInfo{ - {Address: "SPB2", PubKey: []byte{4}, Options: []string{"/Location:Europe/Country:Italy", "/Capacity:10", "/Price:100"}}, - {Address: "MSK1", PubKey: []byte{2}, Options: []string{"/Location:Europe/Country:Germany", "/Capacity:10", "/Price:1"}}, - {Address: "MSK2", PubKey: []byte{3}, Options: []string{"/Location:Europe/Country:Russia", "/Capacity:5", "/Price:10"}}, - {Address: "SPB1", PubKey: []byte{1}, Options: []string{"/Location:Europe/Country:France", "/Capacity:20", "/Price:2"}}, - } - - add := func(nm *NetMap, indices ...int) { - for _, i := range indices { - err := nm.Add(nodes[i].Address, nodes[i].PubKey, 0, nodes[i].Options...) - require.NoError(t, err) - } - } - - indices := []int{0, 1, 2, 3} - - nm1 := NewNetmap() - add(nm1, indices...) - norm := nm1.Normalise() - - for i := 0; i < testCount; i++ { - rand.Seed(time.Now().UnixNano()) - rand.Shuffle(len(indices), func(i, j int) { indices[i], indices[j] = indices[j], indices[i] }) - - nm := NewNetmap() - add(nm, indices...) - require.Equal(t, norm, nm.Normalise()) - } - - t.Run("normalise removes duplicates", func(t *testing.T) { - before := NewNetmap() - add(before, indices...) - before.items = append(before.items, before.items...) - - nm := before.Normalise() - require.Len(t, nm.items, len(indices)) - - loop: - for i := range nodes { - for j := range nm.items { - if bytes.Equal(nm.items[j].PubKey, nodes[i].PubKey) { - continue loop - } - } - require.Fail(t, "normalized netmap does not contain '%s' node", nodes[i].Address) - } - }) -} - -func TestNodeInfo_Price(t *testing.T) { - var info bootstrap.NodeInfo - - // too small value - info = bootstrap.NodeInfo{Options: []string{"/Price:0.01048575"}} - require.Equal(t, uint64(0), info.Price()) - - // min value - info = bootstrap.NodeInfo{Options: []string{"/Price:0.01048576"}} - require.Equal(t, uint64(1), info.Price()) - - // big value - info = bootstrap.NodeInfo{Options: []string{"/Price:1000000000.666"}} - require.Equal(t, uint64(1000000000.666*1e8/object.UnitsMB), info.Price()) -} diff --git a/lib/netmap/storage.go b/lib/netmap/storage.go deleted file mode 100644 index fc26bb555..000000000 --- a/lib/netmap/storage.go +++ /dev/null @@ -1,27 +0,0 @@ -package netmap - -// GetParams is a group of parameters -// for network map receiving operation. -type GetParams struct { -} - -// GetResult is a group of values -// returned by container receiving operation. -type GetResult struct { - nm *NetMap -} - -// Storage is an interface of the storage of NeoFS network map. -type Storage interface { - GetNetMap(GetParams) (*GetResult, error) -} - -// NetMap is a network map getter. -func (s GetResult) NetMap() *NetMap { - return s.nm -} - -// SetNetMap is a network map setter. -func (s *GetResult) SetNetMap(v *NetMap) { - s.nm = v -} diff --git a/lib/netmap/storage_test.go b/lib/netmap/storage_test.go deleted file mode 100644 index 27315f8b5..000000000 --- a/lib/netmap/storage_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package netmap - -import ( - "testing" - - "github.com/nspcc-dev/neofs-api-go/bootstrap" - "github.com/stretchr/testify/require" -) - -func TestGetResult(t *testing.T) { - s := GetResult{} - - nm := NewNetmap() - require.NoError(t, - nm.AddNode(&bootstrap.NodeInfo{ - Address: "address", - PubKey: []byte{1, 2, 3}, - }), - ) - s.SetNetMap(nm) - - require.Equal(t, nm, s.NetMap()) -} diff --git a/lib/peers/peers.go b/lib/peers/peers.go deleted file mode 100644 index 406e495a8..000000000 --- a/lib/peers/peers.go +++ /dev/null @@ -1,455 +0,0 @@ -package peers - -import ( - "context" - "net" - "sync" - "time" - - "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" - "github.com/nspcc-dev/neofs-node/lib/transport" - "github.com/pkg/errors" - "go.uber.org/zap" - "google.golang.org/grpc" - "google.golang.org/grpc/connectivity" - "google.golang.org/grpc/keepalive" - "google.golang.org/grpc/status" -) - -type ( - // Interface is an interface of network connections controller. - Interface interface { - Shutdown() error - Job(context.Context) - Address() multiaddr.Multiaddr - RemoveConnection(maddr multiaddr.Multiaddr) error - Listen(maddr multiaddr.Multiaddr) (manet.Listener, error) - Connect(ctx context.Context, maddr multiaddr.Multiaddr) (manet.Conn, error) - GRPCConnector - } - - // GRPCConnector is an interface of gRPC virtual connector. - GRPCConnector interface { - GRPCConnection(ctx context.Context, maddr multiaddr.Multiaddr, reset bool) (*grpc.ClientConn, error) - } - - // Params groups the parameters of Interface. - Params struct { - Address multiaddr.Multiaddr - Transport transport.Transport - Logger *zap.Logger - Attempts int64 - AttemptsTTL time.Duration - ConnectionTTL time.Duration - ConnectionIDLE time.Duration - MetricsTimeout time.Duration - KeepAliveTTL time.Duration - KeepAlivePingTTL time.Duration - } - - connItem struct { - sync.RWMutex - conn *grpc.ClientConn - used time.Time - } - - iface struct { - log *zap.Logger - addr multiaddr.Multiaddr // self address - tr transport.Transport - tick time.Duration - idle time.Duration - - keepAlive time.Duration - pingTTL time.Duration - - metricsTimeout time.Duration - - grpc struct { - // globalMutex used by garbage collector and other high - globalMutex *sync.RWMutex - // bookMutex resolves concurrent access to the new connection - bookMutex *sync.RWMutex - // connBook contains connection info - // it's mutex resolves concurrent access to existed connection - connBook map[string]*connItem - } - - cons struct { - *sync.RWMutex - items map[string]transport.Connection - } - - lis struct { - *sync.RWMutex - items map[string]manet.Listener - } - } -) - -const ( - defaultAttemptsCount = 5 - defaultAttemptsTTL = 30 * time.Second - defaultCloseTimer = 30 * time.Second - defaultConIdleTTL = 30 * time.Second - defaultKeepAliveTTL = 5 * time.Second - defaultMetricsTimeout = 5 * time.Second - defaultKeepAlivePingTTL = 50 * time.Millisecond -) - -var ( - // ErrDialToSelf is returned if we attempt to dial our own peer - ErrDialToSelf = errors.New("dial to self attempted") - // ErrEmptyAddress returns when you try to create Interface with empty address - ErrEmptyAddress = errors.New("self address could not be empty") - // ErrEmptyTransport returns when you try to create Interface with empty transport - ErrEmptyTransport = errors.New("transport could not be empty") -) - -var errNilMultiaddr = errors.New("empty multi-address") - -func (s *iface) Shutdown() error { - s.lis.Lock() - s.cons.Lock() - s.grpc.globalMutex.Lock() - - defer func() { - s.lis.Unlock() - s.cons.Unlock() - s.grpc.globalMutex.Unlock() - }() - - for addr := range s.cons.items { - if err := s.removeNetConnection(addr); err != nil { - return errors.Wrapf(err, "could not remove net connection `%s`", addr) - } - } - - for addr := range s.grpc.connBook { - if err := s.removeGRPCConnection(addr); err != nil { - return errors.Wrapf(err, "could not remove net connection `%s`", addr) - } - } - - for addr := range s.lis.items { - if err := s.removeListener(addr); err != nil { - return errors.Wrapf(err, "could not remove listener `%s`", addr) - } - } - - return nil -} - -// RemoveConnection from Interface. -// Used only in tests, consider removing. -func (s *iface) RemoveConnection(maddr multiaddr.Multiaddr) error { - addr, err := convertAddress(maddr) - if err != nil { - return err - } - - s.cons.Lock() - s.grpc.globalMutex.Lock() - - defer func() { - s.cons.Unlock() - s.grpc.globalMutex.Unlock() - }() - - // Try to remove connection - if err := s.removeNetConnection(maddr.String()); err != nil { - return errors.Wrapf(err, "could not remove net connection `%s`", maddr.String()) - } - - // Try to remove gRPC connection - if err := s.removeGRPCConnection(addr); err != nil { - return errors.Wrapf(err, "could not remove gRPC connection `%s`", addr) - } - - // TODO remove another connections - - return nil -} - -func (s *iface) removeListener(addr string) error { - if lis, ok := s.lis.items[addr]; ok { - if err := lis.Close(); err != nil { - return err - } - - delete(s.lis.items, addr) - } - - return nil -} - -func (s *iface) removeNetConnection(addr string) error { - // Try to remove simple connection - if con, ok := s.cons.items[addr]; ok { - if err := con.Close(); err != nil { - return err - } - - delete(s.cons.items, addr) - } - - return nil -} - -func (s *iface) removeGRPCConnection(addr string) error { - if gCon, ok := s.grpc.connBook[addr]; ok && gCon.conn != nil { - if err := gCon.conn.Close(); err != nil { - state, ok := status.FromError(err) - if !ok { - return err - } - - s.log.Debug("error state", - zap.String("address", addr), - zap.Any("code", state.Code()), - zap.String("state", state.Message()), - zap.Any("details", state.Details())) - } - } - - delete(s.grpc.connBook, addr) - - return nil -} - -// Connect to address -// Used only in tests, consider removing. -func (s *iface) Connect(ctx context.Context, maddr multiaddr.Multiaddr) (manet.Conn, error) { - var ( - err error - con transport.Connection - ) - - if maddr.Equal(s.addr) { - return nil, ErrDialToSelf - } - - s.cons.RLock() - con, ok := s.cons.items[maddr.String()] - s.cons.RUnlock() - - if ok && !con.Closed() { - return con, nil - } - - if con, err = s.newConnection(ctx, maddr, false); err != nil { - return nil, err - } - - s.cons.Lock() - s.cons.items[maddr.String()] = con - s.cons.Unlock() - - return con, nil -} - -// Listen try to find listener or creates new. -func (s *iface) Listen(maddr multiaddr.Multiaddr) (manet.Listener, error) { - // fixme: concurrency issue there, same as 5260f04d - // but it's not so bad, because `Listen()` used - // once during startup routine. - s.lis.RLock() - lis, ok := s.lis.items[maddr.String()] - s.lis.RUnlock() - - if ok { - return lis, nil - } - - lis, err := s.tr.Listen(maddr) - if err != nil { - return nil, err - } - - s.lis.Lock() - s.lis.items[maddr.String()] = lis - s.lis.Unlock() - - return lis, nil -} - -// Address of current Interface instance. -func (s *iface) Address() multiaddr.Multiaddr { - return s.addr -} - -func isGRPCClosed(con *grpc.ClientConn) bool { - switch con.GetState() { - case connectivity.Idle, connectivity.Connecting, connectivity.Ready: - return false - default: - // connectivity.TransientFailure, connectivity.Shutdown - return true - } -} - -func (s *iface) newConnection(ctx context.Context, addr multiaddr.Multiaddr, reset bool) (transport.Connection, error) { - return s.tr.Dial(ctx, addr, reset) -} - -func gRPCKeepAlive(ping, ttl time.Duration) grpc.DialOption { - return grpc.WithKeepaliveParams(keepalive.ClientParameters{ - Time: ping, - Timeout: ttl, - PermitWithoutStream: true, - }) -} - -func convertAddress(maddr multiaddr.Multiaddr) (string, error) { - if maddr == nil { - return "", errNilMultiaddr - } - - addr, err := manet.ToNetAddr(maddr) - if err != nil { - return "", errors.Wrapf(err, "could not convert address `%s`", maddr) - } - - return addr.String(), nil -} - -// GRPCConnection creates gRPC connection over peers connection. -func (s *iface) GRPCConnection(ctx context.Context, maddr multiaddr.Multiaddr, reset bool) (*grpc.ClientConn, error) { - addr, err := convertAddress(maddr) - if err != nil { - return nil, errors.Wrapf(err, "could not convert `%v`", maddr) - } - - // Get global mutex on read. - // All high level function e.g. peers garbage collector - // or shutdown must use globalMutex.Lock instead - s.grpc.globalMutex.RLock() - - // Get connection item from connection book or create a new one. - // Concurrent map access resolved by bookMutex. - s.grpc.bookMutex.Lock() - - item, ok := s.grpc.connBook[addr] - if !ok { - item = new(connItem) - s.grpc.connBook[addr] = item - } - - s.grpc.bookMutex.Unlock() - - // Now lock connection item. - // This denies concurrent access to the same address, - // but allows concurrent access to a different addresses. - item.Lock() - - if item.conn != nil && !isGRPCClosed(item.conn) { - item.used = time.Now() - - item.Unlock() - s.grpc.globalMutex.RUnlock() - - return item.conn, nil - } - - // Если вышеописанные строки переместить внутрь WithDialer, - // мы получим сломанный коннекшн, но ошибка не будет возвращена, - // поэтому мы сначала проверяем коннекшн и лишь потом возвращаем - // *gRPC.ClientConn - // - // Это будет работать с `grpc.WithBlock()`, см. ниже - conn, err := grpc.DialContext(ctx, maddr.String(), - gRPCKeepAlive(s.pingTTL, s.keepAlive), - // TODO: we must provide grpc.WithInsecure() or set credentials - grpc.WithInsecure(), - grpc.WithBlock(), - grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { - return s.newConnection(ctx, maddr, reset) - }), - ) - if err == nil { - item.conn = conn - item.used = time.Now() - } - - item.Unlock() - s.grpc.globalMutex.RUnlock() - - return conn, err -} - -// New create iface instance and check arguments. -func New(p Params) (Interface, error) { - if p.Address == nil { - return nil, ErrEmptyAddress - } - - if p.Transport == nil { - return nil, ErrEmptyTransport - } - - if p.Attempts <= 0 { - p.Attempts = defaultAttemptsCount - } - - if p.AttemptsTTL <= 0 { - p.AttemptsTTL = defaultAttemptsTTL - } - - if p.ConnectionTTL <= 0 { - p.ConnectionTTL = defaultCloseTimer - } - - if p.ConnectionIDLE <= 0 { - p.ConnectionIDLE = defaultConIdleTTL - } - - if p.KeepAliveTTL <= 0 { - p.KeepAliveTTL = defaultKeepAliveTTL - } - - if p.KeepAlivePingTTL <= 0 { - p.KeepAlivePingTTL = defaultKeepAlivePingTTL - } - - if p.MetricsTimeout <= 0 { - p.MetricsTimeout = defaultMetricsTimeout - } - - return &iface{ - tick: p.ConnectionTTL, - idle: p.ConnectionIDLE, - - keepAlive: p.KeepAliveTTL, - pingTTL: p.KeepAlivePingTTL, - - metricsTimeout: p.MetricsTimeout, - - log: p.Logger, - addr: p.Address, - tr: p.Transport, - grpc: struct { - globalMutex *sync.RWMutex - bookMutex *sync.RWMutex - connBook map[string]*connItem - }{ - globalMutex: new(sync.RWMutex), - bookMutex: new(sync.RWMutex), - connBook: make(map[string]*connItem), - }, - cons: struct { - *sync.RWMutex - items map[string]transport.Connection - }{ - RWMutex: new(sync.RWMutex), - items: make(map[string]transport.Connection), - }, - lis: struct { - *sync.RWMutex - items map[string]manet.Listener - }{ - RWMutex: new(sync.RWMutex), - items: make(map[string]manet.Listener), - }, - }, nil -} diff --git a/lib/peers/peers_test.go b/lib/peers/peers_test.go deleted file mode 100644 index d71ef7b52..000000000 --- a/lib/peers/peers_test.go +++ /dev/null @@ -1,484 +0,0 @@ -package peers - -import ( - "context" - "encoding" - "encoding/json" - "net" - "strings" - "sync" - "testing" - "time" - - "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" - "github.com/nspcc-dev/neofs-node/lib/transport" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" -) - -type ( - fakeAddress struct { - json.Marshaler - json.Unmarshaler - encoding.TextMarshaler - encoding.TextUnmarshaler - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler - } - - // service is used to implement GreaterServer. - service struct{} -) - -// Hello is simple handler -func (*service) Hello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) { - return &HelloResponse{ - Message: "Hello " + req.Name, - }, nil -} - -var _ multiaddr.Multiaddr = (*fakeAddress)(nil) - -func (fakeAddress) Equal(multiaddr.Multiaddr) bool { - return false -} - -func (fakeAddress) Bytes() []byte { - return nil -} - -func (fakeAddress) String() string { - return "fake" -} - -func (fakeAddress) Protocols() []multiaddr.Protocol { - return []multiaddr.Protocol{{Name: "fake"}} -} - -func (fakeAddress) Encapsulate(multiaddr.Multiaddr) multiaddr.Multiaddr { - panic("implement me") -} - -func (fakeAddress) Decapsulate(multiaddr.Multiaddr) multiaddr.Multiaddr { - panic("implement me") -} - -func (fakeAddress) ValueForProtocol(code int) (string, error) { - return "", nil -} - -const testCount = 10 - -func newTestAddress(t *testing.T) multiaddr.Multiaddr { - lis, err := net.Listen("tcp", "0.0.0.0:0") // nolint:gosec - require.NoError(t, err) - require.NoError(t, lis.Close()) - - l, ok := lis.(*net.TCPListener) - require.True(t, ok) - - _, port, err := net.SplitHostPort(l.Addr().String()) - require.NoError(t, err) - - items := []string{ - "ip4", - "127.0.0.1", - "tcp", - port, - } - - maddr, err := multiaddr.NewMultiaddr("/" + strings.Join(items, "/")) - require.NoError(t, err) - - return maddr -} - -func createTestInterface(t *testing.T) Interface { - s, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - return s -} - -func createTestInterfaces(t *testing.T) []Interface { - var ifaces = make([]Interface, 0, testCount) - for i := 0; i < testCount; i++ { - ifaces = append(ifaces, createTestInterface(t)) - } - return ifaces -} - -func connectEachOther(t *testing.T, ifaces []Interface) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - for _, s := range ifaces { - _, err := s.Listen(s.Address()) - require.NoError(t, err) - - for _, n := range ifaces { - if s.Address().Equal(n.Address()) { - continue // do not connect itself - } - - _, err = n.Connect(ctx, s.Address()) - require.NoError(t, err) - } - } -} - -func TestInterface(t *testing.T) { - t.Run("should fail on empty address", func(t *testing.T) { - _, err := New(Params{}) - require.EqualError(t, err, ErrEmptyAddress.Error()) - }) - - t.Run("should fail on empty transport", func(t *testing.T) { - _, err := New(Params{Address: newTestAddress(t)}) - require.EqualError(t, err, ErrEmptyTransport.Error()) - }) - - t.Run("try to create multiple Interface and connect each other", func(t *testing.T) { - ifaces := createTestInterfaces(t) - connectEachOther(t, ifaces) - }) - - t.Run("should fail on itself connection", func(t *testing.T) { - s := createTestInterface(t) - _, err := s.Connect(context.Background(), s.Address()) - require.EqualError(t, err, ErrDialToSelf.Error()) - }) - - t.Run("should fail when you try to remove closed connection", func(t *testing.T) { - s1, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - s2, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - _, err = s1.Listen(s1.Address()) - require.NoError(t, err) - - _, err = s2.Listen(s2.Address()) - require.NoError(t, err) - - con, err := s1.Connect(context.Background(), s2.Address()) - require.NoError(t, err) - require.NoError(t, con.Close()) - - err = s1.RemoveConnection(s2.Address()) - require.NoError(t, err) - }) - - t.Run("should not create connection / listener twice", func(t *testing.T) { - s1, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - s2, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - l1, err := s1.Listen(s1.Address()) - require.NoError(t, err) - - l2, err := s1.Listen(s1.Address()) - require.NoError(t, err) - - require.Equal(t, l1, l2) - - _, err = s2.Listen(s2.Address()) - require.NoError(t, err) - - c1, err := s1.Connect(context.Background(), s2.Address()) - require.NoError(t, err) - - c2, err := s1.Connect(context.Background(), s2.Address()) - require.NoError(t, err) - - require.Equal(t, c1, c2) - require.NoError(t, c1.Close()) - - err = s1.RemoveConnection(s2.Address()) - require.NoError(t, err) - }) - - t.Run("should not try to close unknown connection", func(t *testing.T) { - s1, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - s2, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - l1, err := s1.Listen(s1.Address()) - require.NoError(t, err) - - l2, err := s1.Listen(s1.Address()) - require.NoError(t, err) - - require.Equal(t, l1, l2) - - _, err = s2.Listen(s2.Address()) - require.NoError(t, err) - - _, err = s1.Connect(context.Background(), s2.Address()) - require.NoError(t, err) - - err = s1.RemoveConnection(s2.Address()) - require.NoError(t, err) - - err = s1.RemoveConnection(s2.Address()) - require.NoError(t, err) - }) - - t.Run("should shutdown without errors", func(t *testing.T) { - s1, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - s2, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - l1, err := s1.Listen(s1.Address()) - require.NoError(t, err) - - l2, err := s1.Listen(s1.Address()) - require.NoError(t, err) - - require.Equal(t, l1, l2) - - _, err = s2.Listen(s2.Address()) - require.NoError(t, err) - - _, err = s1.Connect(context.Background(), s2.Address()) - require.NoError(t, err) - - err = s1.Shutdown() - require.NoError(t, err) - - err = s2.Shutdown() - require.NoError(t, err) - }) - - t.Run("should fail, when shutdown with closed connections or listeners", func(t *testing.T) { - s1, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - s2, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - l1, err := s1.Listen(s1.Address()) - require.NoError(t, err) - - l2, err := s1.Listen(s1.Address()) - require.NoError(t, err) - - require.Equal(t, l1, l2) - - lis, err := s2.Listen(s2.Address()) - require.NoError(t, err) - - con, err := s1.Connect(context.Background(), s2.Address()) - require.NoError(t, err) - - require.NoError(t, con.Close()) - - err = s1.Shutdown() - require.NoError(t, err) - - require.NoError(t, lis.Close()) - - err = s2.Shutdown() - require.Error(t, err) - }) - - t.Run("circuit breaker should start fail connection after N-fails", func(t *testing.T) { - s1, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - addr := newTestAddress(t) - for i := 0; i < defaultAttemptsCount*2; i++ { - _, err = s1.Connect(context.Background(), addr) - require.Error(t, err) - - if i+1 == defaultAttemptsCount { - _, err = s1.Listen(addr) - require.NoError(t, err) - } - } - }) - - t.Run("should return error on bad multi-address", func(t *testing.T) { - s1, err := New(Params{ - Address: newTestAddress(t), - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - _, err = s1.Listen(&fakeAddress{}) - require.Error(t, err) - }) - - t.Run("gRPC connection test", func(t *testing.T) { - var ( - err error - s1, s2 Interface - h = &service{} - g = grpc.NewServer() - a1, a2 = newTestAddress(t), newTestAddress(t) - tr = transport.New(5, time.Second) - _ = h - done = make(chan struct{}) - ) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - s1, err = New(Params{ - Address: a1, - Transport: tr, - }) - require.NoError(t, err) - - s2, err = New(Params{ - Address: a2, - Transport: tr, - }) - require.NoError(t, err) - - RegisterGreeterServer(g, h) // register service - - l, err := s1.Listen(a1) - require.NoError(t, err) - - defer l.Close() // nolint:golint - - wg := new(sync.WaitGroup) - wg.Add(1) - - go func() { - close(done) - - _ = g.Serve(manet.NetListener(l)) - - wg.Done() - }() - - <-done // wait for server is start listening connections: - - // Fail connection - con, err := s2.GRPCConnection(ctx, &fakeAddress{}, false) - require.Nil(t, con) - require.Error(t, err) - - con, err = s2.GRPCConnection(ctx, a1, false) - require.NoError(t, err) - - cli := NewGreeterClient(con) - resp, err := cli.Hello(ctx, &HelloRequest{ - Name: "Interface test", - }) - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, "Hello Interface test", resp.Message) - - g.GracefulStop() - - wg.Wait() - }) - - t.Run("test grpc connections", func(t *testing.T) { - var ( - ifaces = make([]Interface, 0, testCount) - addresses = make([]multiaddr.Multiaddr, 0, testCount) - ) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - for i := 0; i < testCount; i++ { - addresses = append(addresses, newTestAddress(t)) - - s, err := New(Params{ - Address: addresses[i], - Transport: transport.New(5, time.Second), - }) - require.NoError(t, err) - - lis, err := s.Listen(addresses[i]) - require.NoError(t, err) - - svc := &service{} - srv := grpc.NewServer() - - RegisterGreeterServer(srv, svc) - - ifaces = append(ifaces, s) - - go func() { - l := manet.NetListener(lis) - require.NoError(t, srv.Serve(l)) - }() - } - - const reqName = "test" - wg := new(sync.WaitGroup) - - for i := 0; i < testCount; i++ { - for j := 0; j < testCount; j++ { - wg.Add(1) - go func(i, j int) { - defer wg.Done() - - con, err := ifaces[i].GRPCConnection(ctx, addresses[j], false) - require.NoError(t, err) - - cli := NewGreeterClient(con) - - resp, err := cli.Hello(ctx, &HelloRequest{Name: reqName}) - require.NoError(t, err) - - require.Equal(t, "Hello "+reqName, resp.Message) - - require.NoError(t, con.Close()) - }(i, j) - - } - } - - wg.Wait() - }) -} diff --git a/lib/storage/storage.go b/lib/storage/storage.go deleted file mode 100644 index 11775c3d2..000000000 --- a/lib/storage/storage.go +++ /dev/null @@ -1,122 +0,0 @@ -package storage - -import ( - "io" - - "github.com/nspcc-dev/neofs-node/lib/buckets" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/pkg/errors" - "github.com/spf13/viper" - "go.uber.org/zap" -) - -type ( - store struct { - blob core.Bucket - - meta core.Bucket - - spaceMetrics core.Bucket - } - - sizer interface { - Size() int64 - } - - // Params for create Core.Storage component - Params struct { - Buckets []core.BucketType - Viper *viper.Viper - Logger *zap.Logger - } -) - -// New creates Core.Storage component. -func New(p Params) (core.Storage, error) { - var ( - err error - bs = make(map[core.BucketType]core.Bucket) - ) - - for _, name := range p.Buckets { - if bs[name], err = buckets.NewBucket(name, p.Logger, p.Viper); err != nil { - return nil, err - } - } - - return &store{ - blob: bs[core.BlobStore], - - meta: bs[core.MetaStore], - - spaceMetrics: bs[core.SpaceMetricsStore], - }, nil -} - -// GetBucket returns available bucket by type or an error. -func (s *store) GetBucket(name core.BucketType) (core.Bucket, error) { - switch name { - case core.BlobStore: - if s.blob == nil { - return nil, errors.Errorf("bucket(`%s`) not initialized", core.BlobStore) - } - - return s.blob, nil - case core.MetaStore: - if s.meta == nil { - return nil, errors.Errorf("bucket(`%s`) not initialized", core.MetaStore) - } - - return s.meta, nil - case core.SpaceMetricsStore: - if s.spaceMetrics == nil { - return nil, errors.Errorf("bucket(`%s`) not initialized", core.SpaceMetricsStore) - } - - return s.spaceMetrics, nil - default: - return nil, errors.Errorf("bucket for type `%s` not implemented", name) - } -} - -// Size of all buckets. -func (s *store) Size() int64 { - var ( - all int64 - sizers = []sizer{ - s.blob, - s.meta, - s.spaceMetrics, - } - ) - - for _, item := range sizers { - if item == nil { - continue - } - - all += item.Size() - } - - return all -} - -// Close all buckets. -func (s *store) Close() error { - var closers = []io.Closer{ - s.blob, - s.meta, - } - - for _, item := range closers { - if item == nil { - continue - } - - if err := item.Close(); err != nil { - return err - } - } - - return nil -} diff --git a/lib/transport/connection.go b/lib/transport/connection.go deleted file mode 100644 index bb051b4a9..000000000 --- a/lib/transport/connection.go +++ /dev/null @@ -1,39 +0,0 @@ -package transport - -import ( - "sync/atomic" - - manet "github.com/multiformats/go-multiaddr-net" -) - -type ( - // Connection is an interface of network connection. - Connection interface { - manet.Conn - Closed() bool - } - - conn struct { - manet.Conn - closed *int32 - } -) - -func newConnection(con manet.Conn) Connection { - return &conn{ - Conn: con, - closed: new(int32), - } -} - -// Closed checks that connection closed. -func (c *conn) Closed() bool { return atomic.LoadInt32(c.closed) == 1 } - -// Close connection and write state. -func (c *conn) Close() error { - if atomic.CompareAndSwapInt32(c.closed, 0, 1) { - return c.Conn.Close() - } - - return nil -} diff --git a/lib/transport/transport.go b/lib/transport/transport.go deleted file mode 100644 index 4e06fedd3..000000000 --- a/lib/transport/transport.go +++ /dev/null @@ -1,76 +0,0 @@ -package transport - -import ( - "context" - "fmt" - "time" - - "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" - circuit "github.com/rubyist/circuitbreaker" -) - -type ( - // Transport is an interface of network connection listener. - Transport interface { - Dial(context.Context, multiaddr.Multiaddr, bool) (Connection, error) - Listen(multiaddr.Multiaddr) (manet.Listener, error) - } - - transport struct { - threshold int64 - timeout time.Duration - panel *circuit.Panel - } -) - -const defaultBreakerName = "_NeoFS" - -func (t *transport) Dial(ctx context.Context, addr multiaddr.Multiaddr, reset bool) (Connection, error) { - var ( - con manet.Conn - breaker = t.breakerLookup(addr) - ) - - if reset { - breaker.Reset() - } - - err := breaker.CallContext(ctx, func() (errCall error) { - var d manet.Dialer - con, errCall = d.DialContext(ctx, addr) - return errCall - }, t.timeout) - - if err != nil { - return nil, err - } - - return newConnection(con), nil -} - -func (t *transport) Listen(addr multiaddr.Multiaddr) (manet.Listener, error) { - return manet.Listen(addr) -} - -func (t *transport) breakerLookup(addr fmt.Stringer) *circuit.Breaker { - panel := defaultBreakerName + addr.String() - - cb, ok := t.panel.Get(panel) - if !ok { - cb = circuit.NewConsecutiveBreaker(t.threshold) - t.panel.Add(panel, cb) - } - - return cb -} - -// New is a transport component's constructor. -func New(threshold int64, timeout time.Duration) Transport { - breaker := circuit.NewConsecutiveBreaker(threshold) - - panel := circuit.NewPanel() - panel.Add(defaultBreakerName, breaker) - - return &transport{panel: panel, threshold: threshold, timeout: timeout} -} diff --git a/lib/transport/transport_test.go b/lib/transport/transport_test.go deleted file mode 100644 index bd3bd2838..000000000 --- a/lib/transport/transport_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package transport - -import ( - "context" - "net" - "testing" - "time" - - manet "github.com/multiformats/go-multiaddr-net" - circuit "github.com/rubyist/circuitbreaker" - "github.com/stretchr/testify/require" -) - -func TestTransport(t *testing.T) { - var ( - attempts = int64(5) - lc net.ListenConfig - tr = New(attempts, time.Second) - ctx, cancel = context.WithCancel(context.TODO()) - ) - - defer cancel() - - lis1, err := lc.Listen(ctx, "tcp", ":0") - require.NoError(t, err) - - addr1, err := manet.FromNetAddr(lis1.Addr()) - require.NoError(t, err) - - _, err = tr.Dial(ctx, addr1, false) - require.NoError(t, err) - - lis2, err := lc.Listen(ctx, "tcp", ":0") - require.NoError(t, err) - - addr2, err := manet.FromNetAddr(lis2.Addr()) - require.NoError(t, err) - - _, err = tr.Dial(ctx, addr1, false) - require.NoError(t, err) - - require.NoError(t, lis1.Close()) - - for i := int64(0); i < 10; i++ { - _, err = tr.Dial(ctx, addr1, false) - require.Error(t, err) - - if i >= attempts { - require.EqualError(t, err, circuit.ErrBreakerOpen.Error()) - } - - _, err = tr.Dial(ctx, addr2, false) - require.NoError(t, err) - } - - time.Sleep(time.Second) - - _, err = tr.Dial(ctx, addr1, false) - require.Error(t, err) - require.NotContains(t, err.Error(), circuit.ErrBreakerOpen.Error()) -} diff --git a/misc/build.go b/misc/build.go index f4dab3063..61b4613ef 100644 --- a/misc/build.go +++ b/misc/build.go @@ -1,12 +1,21 @@ package misc const ( - // NodeName is an application name. + // NodeName is a neofs node application name. NodeName = "neofs-node" - // Prefix is an application prefix. + // Prefix is a neofs node application prefix. Prefix = "neofs" + // InnerRingName is an inner ring application name. + InnerRingName = "neofs-ir" + + // InnerRingPrefix is an inner ring application prefix. + InnerRingPrefix = "neofs_ir" +) + +// These variables are changed in compile time. +var ( // Build is an application build time. Build = "now" diff --git a/modules/morph/goclient.go b/modules/morph/goclient.go deleted file mode 100644 index dd0359f2c..000000000 --- a/modules/morph/goclient.go +++ /dev/null @@ -1,32 +0,0 @@ -package morph - -import ( - "context" - "crypto/ecdsa" - - "github.com/nspcc-dev/neo-go/pkg/config/netmode" - "github.com/nspcc-dev/neofs-node/lib/blockchain/goclient" - "github.com/spf13/viper" - "go.uber.org/dig" - "go.uber.org/zap" -) - -type morphClientParams struct { - dig.In - - Viper *viper.Viper - - Logger *zap.Logger - - Key *ecdsa.PrivateKey -} - -func newMorphClient(p morphClientParams) (*goclient.Client, error) { - return goclient.New(context.Background(), &goclient.Params{ - Log: p.Logger, - Key: p.Key, - Endpoint: p.Viper.GetString(optPath(prefix, endpointOpt)), - DialTimeout: p.Viper.GetDuration(optPath(prefix, dialTimeoutOpt)), - Magic: netmode.Magic(p.Viper.GetUint32(optPath(prefix, magicNumberOpt))), - }) -} diff --git a/modules/morph/reputation.go b/modules/morph/reputation.go deleted file mode 100644 index e8c12434c..000000000 --- a/modules/morph/reputation.go +++ /dev/null @@ -1,59 +0,0 @@ -package morph - -import ( - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/peers" - "github.com/pkg/errors" - "go.uber.org/dig" -) - -type reputationContractResult struct { - dig.Out - - ReputationContract implementations.MorphReputationContract -} - -const ( - reputationContractName = "reputation" - - reputationContractPutOpt = "put_method" - - reputationContractListOpt = "list_method" -) - -// ReputationContractPutOptPath returns the config path to put method of Reputation contract. -func ReputationContractPutOptPath() string { - return optPath(prefix, reputationContractName, reputationContractPutOpt) -} - -// ReputationContractListOptPath returns the config path to list method of Reputation contract. -func ReputationContractListOptPath() string { - return optPath(prefix, reputationContractName, reputationContractListOpt) -} - -func newReputationContract(p contractParams, ps peers.Store) (res reputationContractResult, err error) { - cli, ok := p.MorphContracts[reputationContractName] - if !ok { - err = errors.Errorf("missing %s contract client", reputationContractName) - return - } - - morphClient := implementations.MorphReputationContract{} - morphClient.SetReputationContractClient(cli) - morphClient.SetPublicKeyStore(ps) - - morphClient.SetPutMethodName( - p.Viper.GetString( - ReputationContractPutOptPath(), - ), - ) - morphClient.SetListMethodName( - p.Viper.GetString( - ReputationContractListOptPath(), - ), - ) - - res.ReputationContract = morphClient - - return -} diff --git a/modules/node/core.go b/modules/node/core.go deleted file mode 100644 index 665836eee..000000000 --- a/modules/node/core.go +++ /dev/null @@ -1,29 +0,0 @@ -package node - -import ( - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/storage" - "github.com/spf13/viper" - "go.uber.org/zap" -) - -func listBuckets(v *viper.Viper) []core.BucketType { - var ( - items = v.GetStringMap("storage") - result = make([]core.BucketType, 0, len(items)) - ) - - for name := range items { - result = append(result, core.BucketType(name)) - } - - return result -} - -func newStorage(l *zap.Logger, v *viper.Viper) (core.Storage, error) { - return storage.New(storage.Params{ - Viper: v, - Logger: l, - Buckets: listBuckets(v), - }) -} diff --git a/modules/node/placement.go b/modules/node/placement.go deleted file mode 100644 index 9834f7b60..000000000 --- a/modules/node/placement.go +++ /dev/null @@ -1,33 +0,0 @@ -package node - -import ( - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/placement" - "go.uber.org/dig" -) - -type ( - placementToolParams struct { - dig.In - - Placement placement.Component - } - - placementToolResult struct { - dig.Out - - Placer implementations.ObjectPlacer - - Receiver implementations.EpochReceiver - } -) - -func newPlacementTool(p placementToolParams) (res placementToolResult, err error) { - if res.Placer, err = implementations.NewObjectPlacer(p.Placement); err != nil { - return - } - - res.Receiver = res.Placer - - return -} diff --git a/modules/node/services.go b/modules/node/services.go deleted file mode 100644 index d6c3cadca..000000000 --- a/modules/node/services.go +++ /dev/null @@ -1,36 +0,0 @@ -package node - -import ( - "github.com/nspcc-dev/neofs-node/modules/grpc" - "github.com/nspcc-dev/neofs-node/services/metrics" - "github.com/nspcc-dev/neofs-node/services/public/accounting" - "github.com/nspcc-dev/neofs-node/services/public/container" - "github.com/nspcc-dev/neofs-node/services/public/object" - "github.com/nspcc-dev/neofs-node/services/public/session" - "github.com/nspcc-dev/neofs-node/services/public/state" - "go.uber.org/dig" -) - -type servicesParams struct { - dig.In - - Status state.Service - Container container.Service - Object object.Service - Session session.Service - Accounting accounting.Service - Metrics metrics.Service -} - -func attachServices(p servicesParams) grpc.ServicesResult { - return grpc.ServicesResult{ - Services: []grpc.Service{ - p.Status, - p.Container, - p.Accounting, - p.Metrics, - p.Session, - p.Object, - }, - } -} diff --git a/pkg/core/container/acl/basic/enum.go b/pkg/core/container/acl/basic/enum.go new file mode 100644 index 000000000..e4de0cddd --- /dev/null +++ b/pkg/core/container/acl/basic/enum.go @@ -0,0 +1,24 @@ +package basic + +const ( + // OpGetRangeHash is an index of GetRangeHash operation in basic ACL bitmask order. + OpGetRangeHash uint8 = iota + + // OpGetRange is an index of GetRange operation in basic ACL bitmask order. + OpGetRange + + // OpSearch is an index of Search operation in basic ACL bitmask order. + OpSearch + + // OpDelete is an index of Delete operation in basic ACL bitmask order. + OpDelete + + // OpPut is an index of Put operation in basic ACL bitmask order. + OpPut + + // OpHead is an index of Head operation in basic ACL bitmask order. + OpHead + + // OpGet is an index of Get operation in basic ACL bitmask order. + OpGet +) diff --git a/pkg/core/container/acl/basic/types.go b/pkg/core/container/acl/basic/types.go new file mode 100644 index 000000000..682fff36a --- /dev/null +++ b/pkg/core/container/acl/basic/types.go @@ -0,0 +1,159 @@ +package basic + +// ACL represents a container's +// basic permission bits. +type ACL uint32 + +const ( + reservedBitNumber = 2 // first left bits are reserved + + stickyBitPos = reservedBitNumber // X-bit after reserved bits + + finalBitPos = stickyBitPos + 1 // F-bit after X-bit +) + +const ( + opOffset = finalBitPos + 1 // offset of operation bits + + bitsPerOp = 4 // number of bits per operation + + opNumber = 7 // number of operation bit sections +) + +const ( + bitUser uint8 = iota + bitSystem + bitOthers + bitBearer +) + +const leftACLBitPos = opOffset + bitsPerOp*opNumber - 1 + +// returns true if n-th left bit is set (starting at 0). +func isLeftBitSet(value ACL, n uint8) bool { + bitMask := ACL(1 << (leftACLBitPos - n)) + return bitMask != 0 && value&bitMask == bitMask +} + +// sets n-th left bit (starting at 0). +func setLeftBit(value *ACL, n uint8) { + *value |= ACL(1 << (leftACLBitPos - n)) +} + +// resets n-th left bit (starting at 0). +func resetLeftBit(value *ACL, n uint8) { + *value &= ^ACL(1 << (leftACLBitPos - n)) +} + +// Reserved returns true if n-th reserved option is enabled in basic ACL. +func (a ACL) Reserved(n uint8) bool { + return n < reservedBitNumber && isLeftBitSet(a, n) +} + +// SetReserved enables the n-th reserved option in basic ACL. +func (a *ACL) SetReserved(bit uint8) { + if bit < reservedBitNumber { + setLeftBit(a, bit) + } +} + +// ResetReserved disables the n-th reserved option in basic ACL. +func (a *ACL) ResetReserved(bit uint8) { + if bit < reservedBitNumber { + resetLeftBit(a, bit) + } +} + +// Final returns true if final option is enabled in basic ACL. +func (a ACL) Final() bool { + return isLeftBitSet(a, finalBitPos) +} + +// SetFinal enables final option in basic ACL. +func (a *ACL) SetFinal() { + setLeftBit(a, finalBitPos) +} + +// ResetFinal disables final option in basic ACL. +func (a *ACL) ResetFinal() { + resetLeftBit(a, finalBitPos) +} + +// Sticky returns true if sticky option is enabled in basic ACL. +func (a ACL) Sticky() bool { + return isLeftBitSet(a, stickyBitPos) +} + +// SetSticky enables the sticky option in basic ACL. +func (a *ACL) SetSticky() { + setLeftBit(a, stickyBitPos) +} + +// ResetSticky disables the sticky option in basic ACL. +func (a *ACL) ResetSticky() { + resetLeftBit(a, stickyBitPos) +} + +// UserAllowed returns true if user allowed the n-th operation in basic ACL. +func (a ACL) UserAllowed(n uint8) bool { + return isLeftBitSet(a, opOffset+n*bitsPerOp+bitUser) +} + +// AllowUser allows user the n-th operation in basic ACL. +func (a *ACL) AllowUser(n uint8) { + setLeftBit(a, opOffset+n*bitsPerOp+bitUser) +} + +// ForbidUser forbids user the n-th operation in basic ACL. +func (a *ACL) ForbidUser(n uint8) { + resetLeftBit(a, opOffset+n*bitsPerOp+bitUser) +} + +// SystemAllowed returns true if System group allowed the n-th operation is set in basic ACL. +func (a ACL) SystemAllowed(n uint8) bool { + if n != OpDelete && n != OpGetRange { + return true + } + + return isLeftBitSet(a, opOffset+n*bitsPerOp+bitSystem) +} + +// AllowSystem allows System group the n-th operation in basic ACL. +func (a *ACL) AllowSystem(op uint8) { + setLeftBit(a, opOffset+op*bitsPerOp+bitSystem) +} + +// ForbidSystem forbids System group the n-th operation in basic ACL. +func (a *ACL) ForbidSystem(op uint8) { + resetLeftBit(a, opOffset+op*bitsPerOp+bitSystem) +} + +// OthersAllowed returns true if Others group allowed the n-th operation is set in basic ACL. +func (a ACL) OthersAllowed(op uint8) bool { + return isLeftBitSet(a, opOffset+op*bitsPerOp+bitOthers) +} + +// AllowOthers allows Others group the n-th operation in basic ACL. +func (a *ACL) AllowOthers(op uint8) { + setLeftBit(a, opOffset+op*bitsPerOp+bitOthers) +} + +// ForbidOthers forbids Others group the n-th operation in basic ACL. +func (a *ACL) ForbidOthers(op uint8) { + resetLeftBit(a, opOffset+op*bitsPerOp+bitOthers) +} + +// BearerAllowed returns true if Bearer token usage is allowed for n-th operation in basic ACL. +func (a ACL) BearerAllowed(op uint8) bool { + return isLeftBitSet(a, opOffset+op*bitsPerOp+bitBearer) +} + +// AllowBearer allows Bearer token usage for n-th operation in basic ACL. +func (a *ACL) AllowBearer(op uint8) { + setLeftBit(a, opOffset+op*bitsPerOp+bitBearer) +} + +// ForbidBearer forbids Bearer token usage for n-th operation in basic ACL. +func (a *ACL) ForbidBearer(op uint8) { + resetLeftBit(a, opOffset+op*bitsPerOp+bitBearer) +} diff --git a/pkg/core/container/acl/basic/types_test.go b/pkg/core/container/acl/basic/types_test.go new file mode 100644 index 000000000..7628aec13 --- /dev/null +++ b/pkg/core/container/acl/basic/types_test.go @@ -0,0 +1,189 @@ +package basic + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestACLValues(t *testing.T) { + t.Run("private", func(t *testing.T) { + acl := FromUint32(0x1C8C8CCC) + + require.False(t, acl.Reserved(0)) + require.False(t, acl.Reserved(1)) + require.False(t, acl.Sticky()) + require.True(t, acl.Final()) + + require.True(t, acl.UserAllowed(OpGetRangeHash)) + require.True(t, acl.SystemAllowed(OpGetRangeHash)) + require.False(t, acl.OthersAllowed(OpGetRangeHash)) + require.False(t, acl.BearerAllowed(OpGetRangeHash)) + + require.True(t, acl.UserAllowed(OpGetRange)) + require.False(t, acl.SystemAllowed(OpGetRange)) + require.False(t, acl.OthersAllowed(OpGetRange)) + require.False(t, acl.BearerAllowed(OpGetRange)) + + require.True(t, acl.UserAllowed(OpSearch)) + require.True(t, acl.SystemAllowed(OpSearch)) + require.False(t, acl.OthersAllowed(OpSearch)) + require.False(t, acl.BearerAllowed(OpSearch)) + + require.True(t, acl.UserAllowed(OpDelete)) + require.False(t, acl.SystemAllowed(OpDelete)) + require.False(t, acl.OthersAllowed(OpDelete)) + require.False(t, acl.BearerAllowed(OpDelete)) + + require.True(t, acl.UserAllowed(OpPut)) + require.True(t, acl.SystemAllowed(OpPut)) + require.False(t, acl.OthersAllowed(OpPut)) + require.False(t, acl.BearerAllowed(OpPut)) + + require.True(t, acl.UserAllowed(OpHead)) + require.True(t, acl.SystemAllowed(OpHead)) + require.False(t, acl.OthersAllowed(OpHead)) + require.False(t, acl.BearerAllowed(OpHead)) + + require.True(t, acl.UserAllowed(OpGet)) + require.True(t, acl.SystemAllowed(OpGet)) + require.False(t, acl.OthersAllowed(OpGet)) + require.False(t, acl.BearerAllowed(OpGet)) + }) + + t.Run("public with X-bit", func(t *testing.T) { + acl := FromUint32(0x3FFFFFFF) + + require.False(t, acl.Reserved(0)) + require.False(t, acl.Reserved(1)) + require.True(t, acl.Sticky()) + require.True(t, acl.Final()) + + require.True(t, acl.UserAllowed(OpGetRangeHash)) + require.True(t, acl.SystemAllowed(OpGetRangeHash)) + require.True(t, acl.OthersAllowed(OpGetRangeHash)) + require.True(t, acl.BearerAllowed(OpGetRangeHash)) + + require.True(t, acl.UserAllowed(OpGetRange)) + require.True(t, acl.SystemAllowed(OpGetRange)) + require.True(t, acl.OthersAllowed(OpGetRange)) + require.True(t, acl.BearerAllowed(OpGetRange)) + + require.True(t, acl.UserAllowed(OpSearch)) + require.True(t, acl.SystemAllowed(OpSearch)) + require.True(t, acl.OthersAllowed(OpSearch)) + require.True(t, acl.BearerAllowed(OpSearch)) + + require.True(t, acl.UserAllowed(OpDelete)) + require.True(t, acl.SystemAllowed(OpDelete)) + require.True(t, acl.OthersAllowed(OpDelete)) + require.True(t, acl.BearerAllowed(OpDelete)) + + require.True(t, acl.UserAllowed(OpPut)) + require.True(t, acl.SystemAllowed(OpPut)) + require.True(t, acl.OthersAllowed(OpPut)) + require.True(t, acl.BearerAllowed(OpPut)) + + require.True(t, acl.UserAllowed(OpHead)) + require.True(t, acl.SystemAllowed(OpHead)) + require.True(t, acl.OthersAllowed(OpHead)) + require.True(t, acl.BearerAllowed(OpHead)) + + require.True(t, acl.UserAllowed(OpGet)) + require.True(t, acl.SystemAllowed(OpGet)) + require.True(t, acl.OthersAllowed(OpGet)) + require.True(t, acl.BearerAllowed(OpGet)) + }) + + t.Run("read only", func(t *testing.T) { + acl := FromUint32(0x1FFFCCFF) + + require.False(t, acl.Reserved(0)) + require.False(t, acl.Reserved(1)) + require.False(t, acl.Sticky()) + require.True(t, acl.Final()) + + require.True(t, acl.UserAllowed(OpGetRangeHash)) + require.True(t, acl.SystemAllowed(OpGetRangeHash)) + require.True(t, acl.OthersAllowed(OpGetRangeHash)) + require.True(t, acl.BearerAllowed(OpGetRangeHash)) + + require.True(t, acl.UserAllowed(OpGetRange)) + require.True(t, acl.SystemAllowed(OpGetRange)) + require.True(t, acl.OthersAllowed(OpGetRange)) + require.True(t, acl.BearerAllowed(OpGetRange)) + + require.True(t, acl.UserAllowed(OpSearch)) + require.True(t, acl.SystemAllowed(OpSearch)) + require.True(t, acl.OthersAllowed(OpSearch)) + require.True(t, acl.BearerAllowed(OpSearch)) + + require.True(t, acl.UserAllowed(OpDelete)) + require.True(t, acl.SystemAllowed(OpDelete)) + require.False(t, acl.OthersAllowed(OpDelete)) + require.False(t, acl.BearerAllowed(OpDelete)) + + require.True(t, acl.UserAllowed(OpPut)) + require.True(t, acl.SystemAllowed(OpPut)) + require.False(t, acl.OthersAllowed(OpPut)) + require.False(t, acl.BearerAllowed(OpPut)) + + require.True(t, acl.UserAllowed(OpHead)) + require.True(t, acl.SystemAllowed(OpHead)) + require.True(t, acl.OthersAllowed(OpHead)) + require.True(t, acl.BearerAllowed(OpHead)) + + require.True(t, acl.UserAllowed(OpGet)) + require.True(t, acl.SystemAllowed(OpGet)) + require.True(t, acl.OthersAllowed(OpGet)) + require.True(t, acl.BearerAllowed(OpGet)) + }) +} + +func TestACLMethods(t *testing.T) { + acl := new(ACL) + + for i := uint8(0); i < reservedBitNumber; i++ { + acl.SetReserved(i) + require.True(t, acl.Reserved(i)) + acl.ResetReserved(i) + require.False(t, acl.Reserved(i)) + } + + acl.SetSticky() + require.True(t, acl.Sticky()) + acl.ResetSticky() + require.False(t, acl.Sticky()) + + acl.SetFinal() + require.True(t, acl.Final()) + acl.ResetFinal() + require.False(t, acl.Final()) + + for i := OpGetRangeHash; i <= OpGet; i++ { + acl.AllowUser(i) + require.True(t, acl.UserAllowed(i)) + acl.ForbidUser(i) + require.False(t, acl.UserAllowed(i)) + + acl.AllowOthers(i) + require.True(t, acl.OthersAllowed(i)) + acl.ForbidOthers(i) + require.False(t, acl.OthersAllowed(i)) + + acl.AllowBearer(i) + require.True(t, acl.BearerAllowed(i)) + acl.ForbidBearer(i) + require.False(t, acl.BearerAllowed(i)) + + acl.AllowSystem(i) + require.True(t, acl.SystemAllowed(i)) + acl.ForbidSystem(i) + + if i == OpDelete || i == OpGetRange { + require.False(t, acl.SystemAllowed(i)) + } else { + require.True(t, acl.SystemAllowed(i)) + } + } +} diff --git a/pkg/core/container/acl/basic/util.go b/pkg/core/container/acl/basic/util.go new file mode 100644 index 000000000..77ab5829f --- /dev/null +++ b/pkg/core/container/acl/basic/util.go @@ -0,0 +1,64 @@ +package basic + +import ( + "encoding/binary" + "io" +) + +// Size is a size of ACL +// in a binary form. +const Size = 4 + +// FromUint32 converts builtin +// uint32 value to ACL. +// +// Try to avoid direct cast for +// better portability. +func FromUint32(v uint32) ACL { + return ACL(v) +} + +// ToUint32 converts ACL value +// to builtin uint32. +// +// Try to avoid direct cast for +// better portability. +func ToUint32(v ACL) uint32 { + return uint32(v) +} + +// Equal reports whether e and e2 are the same ACL. +// +// Function defines the relation of equality +// between two ACL. Try to avoid comparison through +// "==" operator for better portability. +func Equal(a, b ACL) bool { + return ToUint32(a) == ToUint32(b) +} + +// Marshal encodes ACL into a +// binary form and returns the result. +// +// Result slice has Size length. +func Marshal(a ACL) []byte { + d := make([]byte, Size) + + binary.BigEndian.PutUint32(d, ToUint32(a)) + + return d +} + +// UnmarshalBinary unmarshals ACL from +// a binary representation. +// +// If buffer size is insufficient, +// io.ErrUnexpectedEOF is returned. +func (a *ACL) UnmarshalBinary(data []byte) error { + if len(data) < Size { + return io.ErrUnexpectedEOF + } + + *a = FromUint32(binary.BigEndian.Uint32(data)) + + return nil +} diff --git a/pkg/core/container/acl/basic/util_test.go b/pkg/core/container/acl/basic/util_test.go new file mode 100644 index 000000000..f48eecf22 --- /dev/null +++ b/pkg/core/container/acl/basic/util_test.go @@ -0,0 +1,36 @@ +package basic + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEqual(t *testing.T) { + require.True(t, + Equal( + FromUint32(1), + FromUint32(1), + ), + ) + + require.False(t, + Equal( + FromUint32(1), + FromUint32(2), + ), + ) +} + +func TestMarshal(t *testing.T) { + a := FromUint32(1) + a2 := new(ACL) + + require.NoError(t, + a2.UnmarshalBinary( + Marshal(a), + ), + ) + + require.True(t, Equal(a, *a2)) +} diff --git a/pkg/core/container/acl/extended/storage/storage.go b/pkg/core/container/acl/extended/storage/storage.go new file mode 100644 index 000000000..bdd0559b9 --- /dev/null +++ b/pkg/core/container/acl/extended/storage/storage.go @@ -0,0 +1,53 @@ +package storage + +import ( + "errors" + + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" + "github.com/nspcc-dev/neofs-node/pkg/core/container" +) + +// CID represents the container identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.ID. +type CID = container.ID + +// Table represents extended ACL rule table. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/eacl/extended.Table. +type Table = eacl.Table + +// Storage is the interface that wraps +// basic methods of extended ACL table storage. +type Storage interface { + // GetEACL reads the table from the storage by identifier. + // It returns any error encountered. + // + // GetEACL must return exactly one non-nil value. + // GetEACL must return ErrNotFound if the table is not in storage. + // + // Implementations must not retain or modify the table + // (even temporarily). + GetEACL(CID) (Table, error) + + // PutEACL saves the table to the underlying storage. + // It returns any error encountered that caused the saving to interrupt. + // + // PutEACL must return extended.ErrNilTable on nil table. + // + // Implementations must not retain or modify the table (even temporarily). + // + // Table rewriting behavior is dictated by implementation. + PutEACL(CID, Table, []byte) error +} + +// ErrNotFound is the error returned when eACL table +// was not found in storage. +var ErrNotFound = errors.New("container not found") + +// ErrNilStorage is the error returned by functions that +// expect a non-nil eACL table storage implementation, +// but received nil. +var ErrNilStorage = errors.New("eACL storage is nil") diff --git a/pkg/core/container/acl/extended/storage/test/storage.go b/pkg/core/container/acl/extended/storage/test/storage.go new file mode 100644 index 000000000..7a5242e50 --- /dev/null +++ b/pkg/core/container/acl/extended/storage/test/storage.go @@ -0,0 +1,48 @@ +package test + +import ( + "sync" + + "github.com/nspcc-dev/neofs-node/pkg/core/container" + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended" + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" +) + +type testStorage struct { + *sync.RWMutex + + items map[container.ID]storage.Table +} + +func (s *testStorage) GetEACL(cid storage.CID) (storage.Table, error) { + s.RLock() + table, ok := s.items[cid] + s.RUnlock() + + if !ok { + return nil, storage.ErrNotFound + } + + return table, nil +} + +func (s *testStorage) PutEACL(cid storage.CID, table storage.Table, _ []byte) error { + if table == nil { + return extended.ErrNilTable + } + + s.Lock() + s.items[cid] = table + s.Unlock() + + return nil +} + +// New creates new eACL table storage +// that stores table in go-builtin map. +func New() storage.Storage { + return &testStorage{ + RWMutex: new(sync.RWMutex), + items: make(map[container.ID]storage.Table), + } +} diff --git a/pkg/core/container/acl/extended/types.go b/pkg/core/container/acl/extended/types.go new file mode 100644 index 000000000..a1442fcf3 --- /dev/null +++ b/pkg/core/container/acl/extended/types.go @@ -0,0 +1,102 @@ +package extended + +import ( + "errors" + + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" + "github.com/nspcc-dev/neofs-node/pkg/core/container" +) + +// FIXME: do not duplicate constants + +// OperationType represents the enumeration +// of different destination types of request. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/eacl.OperationType. +// FIXME: operation type should be defined in core lib. +type OperationType = eacl.OperationType + +// Group represents the enumeration +// of different authorization groups. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/acl/extended.Group. +// FIXME: target should be defined in core lib. +type Group = eacl.Group + +// HeaderType represents the enumeration +// of different types of header. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/eacl.HeaderType. +// FIXME: header type enum should be defined in core lib. +type HeaderType = eacl.HeaderType + +// CID represents the container identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.ID. +type CID = container.ID + +// Header is an interface that wraps +// methods of string key-value header. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/eacl.Header. +// FIXME: header should be defined in core lib. +type Header = eacl.Header + +// Table represents extended ACL rule table. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/eacl.ExtendedACLTable. +// FIXME: eacl table should be defined in core package. +// type Table = eacl.ExtendedACLTable + +// TypedHeaderSource is the interface that wraps +// method for selecting typed headers by type. +type TypedHeaderSource interface { + // HeadersOfType returns the list of key-value headers + // of particular type. + // + // It returns any problem encountered through the boolean + // false value. + HeadersOfType(HeaderType) ([]Header, bool) +} + +// RequestInfo is an interface that wraps +// request with authority methods. +type RequestInfo interface { + TypedHeaderSource + + // CID returns container identifier from request context. + CID() CID + + // Key returns the binary representation of + // author's public key. + // + // Any problem encountered can be reflected + // through an empty slice. + // + // Binary key format is dictated by implementation. + Key() []byte + + // OperationType returns the type of request destination. + // + // Any problem encountered can be reflected + // through OpTypeUnknown value. Caller should handle + // OpTypeUnknown value according to its internal logic. + OperationType() OperationType + + // Group returns the authority group type. + // + // Any problem encountered can be reflected + // through GroupUnknown value. Caller should handle + // TargetUnknown value according to its internal logic. + Group() Group +} + +// ErrNilTable is the error returned by functions that +// expect a non-nil eACL table, but received nil. +var ErrNilTable = errors.New("eACL table is nil") diff --git a/pkg/core/container/container.go b/pkg/core/container/container.go new file mode 100644 index 000000000..8f1282fff --- /dev/null +++ b/pkg/core/container/container.go @@ -0,0 +1,82 @@ +package container + +import ( + "errors" + + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/basic" + "github.com/nspcc-dev/netmap" +) + +// BasicACL represents the basic +// ACL rules. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/basic.ACL. +type BasicACL = basic.ACL + +// PlacementRule represents placement +// rules of the container. +// +// It is a type alias of +// github.com/nspcc-dev/netmap.PlacementRule. +// FIXME: container placement rules should be defined in core lib. +type PlacementRule = netmap.PlacementRule + +// Container represents NeoFS container. +type Container struct { + basicACL BasicACL // basic ACL mask + + ownerID OwnerID // the identifier of container's owner + + salt []byte // unique container bytes + + placementRule PlacementRule // placement rules +} + +// ErrNilContainer is the error returned by functions that +// expect a non-nil container pointer, but received nil. +var ErrNilContainer = errors.New("container is nil") + +// OwnerID returns an ID of the container's owner. +func (c *Container) OwnerID() OwnerID { + return c.ownerID +} + +// SetOwnerID sets the ID of the container's owner. +func (c *Container) SetOwnerID(v OwnerID) { + c.ownerID = v +} + +// Salt returns the container salt. +// +// Slice is returned by reference without copying. +func (c *Container) Salt() []byte { + return c.salt +} + +// SetSalt sets the container salt. +// +// Slice is assigned by reference without copying. +func (c *Container) SetSalt(v []byte) { + c.salt = v +} + +// BasicACL returns the mask of basic container permissions. +func (c *Container) BasicACL() BasicACL { + return c.basicACL +} + +// SetBasicACL sets the mask of basic container permissions. +func (c *Container) SetBasicACL(v BasicACL) { + c.basicACL = v +} + +// PlacementRule returns placement rule of the container. +func (c *Container) PlacementRule() PlacementRule { + return c.placementRule +} + +// SetPlacementRule sets placement rule of the container. +func (c *Container) SetPlacementRule(v PlacementRule) { + c.placementRule = v +} diff --git a/pkg/core/container/container_test.go b/pkg/core/container/container_test.go new file mode 100644 index 000000000..21fc1e152 --- /dev/null +++ b/pkg/core/container/container_test.go @@ -0,0 +1,30 @@ +package container + +import ( + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/basic" + "github.com/stretchr/testify/require" +) + +func TestContainerMethods(t *testing.T) { + c := new(Container) + + acl := basic.FromUint32(1) + c.SetBasicACL(acl) + require.True(t, basic.Equal(acl, c.BasicACL())) + + ownerID := OwnerID{1, 2, 3} + c.SetOwnerID(ownerID) + require.Equal(t, ownerID, c.OwnerID()) + + salt := []byte{4, 5, 6} + c.SetSalt(salt) + require.Equal(t, salt, c.Salt()) + + rule := PlacementRule{ + ReplFactor: 1, + } + c.SetPlacementRule(rule) + require.Equal(t, rule, c.PlacementRule()) +} diff --git a/pkg/core/container/id.go b/pkg/core/container/id.go new file mode 100644 index 000000000..251053c2c --- /dev/null +++ b/pkg/core/container/id.go @@ -0,0 +1,49 @@ +package container + +import ( + "crypto/sha256" + + "github.com/nspcc-dev/neofs-api-go/refs" +) + +// ID represents the +// container identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/refs.CID. +// FIXME: container id should be defined in core package. +type ID = refs.CID + +// OwnerID represents the +// container owner identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/refs.OwnerID. +// FIXME: owner ID should be defined in core lib. +type OwnerID = refs.OwnerID + +// OwnerIDSize is a size of OwnerID +// in a binary form. +const OwnerIDSize = refs.OwnerIDSize + +// CalculateID calculates container identifier +// as SHA256 checksum of the binary form. +// +// If container is nil, ErrNilContainer is returned. +func CalculateID(cnr *Container) (*ID, error) { + if cnr == nil { + return nil, ErrNilContainer + } + + data, err := cnr.MarshalBinary() + if err != nil { + return nil, err + } + + res := new(ID) + sh := sha256.Sum256(data) + + copy(res[:], sh[:]) + + return res, nil +} diff --git a/pkg/core/container/id_test.go b/pkg/core/container/id_test.go new file mode 100644 index 000000000..142375ca8 --- /dev/null +++ b/pkg/core/container/id_test.go @@ -0,0 +1,38 @@ +package container + +import ( + "crypto/sha256" + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/basic" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func TestCalculateID(t *testing.T) { + _, err := CalculateID(nil) + require.True(t, errors.Is(err, ErrNilContainer)) + + cnr := new(Container) + cnr.SetBasicACL(basic.FromUint32(1)) + cnr.SetOwnerID(OwnerID{1, 2, 3}) + cnr.SetSalt([]byte{4, 5, 6}) + + id1, err := CalculateID(cnr) + require.NoError(t, err) + + data, err := cnr.MarshalBinary() + require.NoError(t, err) + + sh := sha256.Sum256(data) + + require.Equal(t, id1.Bytes(), sh[:]) + + // change the container + cnr.SetSalt(append(cnr.Salt(), 1)) + + id2, err := CalculateID(cnr) + require.NoError(t, err) + + require.NotEqual(t, id1, id2) +} diff --git a/pkg/core/container/marshal.go b/pkg/core/container/marshal.go new file mode 100644 index 000000000..1cb938ec2 --- /dev/null +++ b/pkg/core/container/marshal.go @@ -0,0 +1,75 @@ +package container + +import ( + "encoding/binary" + "io" + + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/basic" +) + +const ( + saltLenSize = 2 + + fixedSize = 0 + + basic.Size + + OwnerIDSize + + saltLenSize +) + +// MarshalBinary encodes the container into a binary form +// and returns the result. +func (c *Container) MarshalBinary() ([]byte, error) { + data := make([]byte, binaryContainerSize(c)) + + off := copy(data, basic.Marshal(c.basicACL)) + + off += copy(data[off:], c.ownerID.Bytes()) + + binary.BigEndian.PutUint16(data[off:], uint16(len(c.salt))) + off += saltLenSize + + off += copy(data[off:], c.salt) + + if _, err := c.placementRule.MarshalTo(data[off:]); err != nil { + return nil, err + } + + return data, nil +} + +// UnmarshalBinary unmarshals container from a binary +// representation. +// +// If buffer size is insufficient, io.ErrUnexpectedEOF is returned. +func (c *Container) UnmarshalBinary(data []byte) error { + if len(data) < binaryContainerSize(c) { + return io.ErrUnexpectedEOF + } + + if err := c.basicACL.UnmarshalBinary(data); err != nil { + return err + } + + off := basic.Size + + off += copy(c.ownerID[:], data[off:]) + + saltLen := binary.BigEndian.Uint16(data[off:]) + off += saltLenSize + + c.salt = make([]byte, saltLen) + off += copy(c.salt, data[off:]) + + if err := c.placementRule.Unmarshal(data[off:]); err != nil { + return err + } + + return nil +} + +// returns the length of the container in binary form. +func binaryContainerSize(cnr *Container) int { + return fixedSize + + len(cnr.salt) + + cnr.placementRule.Size() +} diff --git a/pkg/core/container/marshal_test.go b/pkg/core/container/marshal_test.go new file mode 100644 index 000000000..20579b8eb --- /dev/null +++ b/pkg/core/container/marshal_test.go @@ -0,0 +1,26 @@ +package container + +import ( + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/basic" + "github.com/stretchr/testify/require" +) + +func TestContainerMarshal(t *testing.T) { + srcCnr := new(Container) + srcCnr.SetBasicACL(basic.FromUint32(1)) + srcCnr.SetOwnerID(OwnerID{1, 2, 3}) + srcCnr.SetSalt([]byte{4, 5, 6}) + srcCnr.SetPlacementRule(PlacementRule{ + ReplFactor: 3, + }) + + data, err := srcCnr.MarshalBinary() + require.NoError(t, err) + + dstCnr := new(Container) + require.NoError(t, dstCnr.UnmarshalBinary(data)) + + require.Equal(t, srcCnr, dstCnr) +} diff --git a/pkg/core/container/storage/storage.go b/pkg/core/container/storage/storage.go new file mode 100644 index 000000000..9a5faadcb --- /dev/null +++ b/pkg/core/container/storage/storage.go @@ -0,0 +1,79 @@ +package storage + +import ( + "errors" + + "github.com/nspcc-dev/neofs-node/pkg/core/container" +) + +// Container represents the NeoFS container. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.Container. +type Container = container.Container + +// OwnerID represents the container +// owner identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.OwnerID. +type OwnerID = container.OwnerID + +// CID represents the container identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.ID. +type CID = container.ID + +// Storage is an interface that wraps +// basic container storage methods. +type Storage interface { + // Put saves pointed container to the underlying storage. + // It returns calculated container identifier and any error + // encountered that caused the saving to interrupt. + // + // Put must return container.ErrNilContainer on nil-pointer. + // + // Implementations must not modify the container through the pointer (even temporarily). + // Implementations must not retain the container pointer. + // + // Container rewriting behavior is dictated by implementation. + Put(*Container) (*CID, error) + + // Get reads the container from the storage by identifier. + // It returns the pointer to requested container and any error encountered. + // + // Get must return exactly one non-nil value. + // Get must return ErrNotFound if the container is not in storage. + // + // Implementations must not retain the container pointer and modify + // the container through it. + Get(CID) (*Container, error) + + // Delete removes the container from the storage. + // It returns any error encountered that caused the deletion to interrupt. + // + // Delete must return nil if container was successfully deleted. + // + // Behavior when deleting a nonexistent container is dictated by implementation. + Delete(CID) error + + // List returns a list of container identifiers belonging to the specified owner. + // It returns any error encountered that caused the listing to interrupt. + // + // List must return the identifiers of all stored containers if owner pointer is nil. + // List must return the empty list and no error in the absence of containers in storage. + // + // Result slice can be either empty slice or nil, so empty list should be checked + // by comparing with zero length (not nil). + // + // Callers should carefully handle the incomplete list in case of interrupt error. + List(*OwnerID) ([]CID, error) +} + +// ErrNotFound is the error returned when container was not found in storage. +var ErrNotFound = errors.New("container not found") + +// ErrNilStorage is the error returned by functions that +// expect a non-nil container storage implementation, but received nil. +var ErrNilStorage = errors.New("container storage is nil") diff --git a/pkg/core/container/storage/test/storage.go b/pkg/core/container/storage/test/storage.go new file mode 100644 index 000000000..b2fe18ab3 --- /dev/null +++ b/pkg/core/container/storage/test/storage.go @@ -0,0 +1,127 @@ +package test + +import ( + "sync" + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/core/container" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + "github.com/stretchr/testify/require" +) + +type testStorage struct { + *sync.RWMutex + + items map[container.ID]*container.Container +} + +func (s *testStorage) Put(cnr *storage.Container) (*storage.CID, error) { + if cnr == nil { + return nil, container.ErrNilContainer + } + + cid, err := container.CalculateID(cnr) + if err != nil { + return nil, err + } + + s.Lock() + s.items[*cid] = cnr + s.Unlock() + + return cid, nil +} + +func (s *testStorage) Get(cid storage.CID) (*storage.Container, error) { + s.RLock() + cnr, ok := s.items[cid] + s.RUnlock() + + if !ok { + return nil, storage.ErrNotFound + } + + return cnr, nil +} + +func (s *testStorage) Delete(cid storage.CID) error { + s.Lock() + delete(s.items, cid) + s.Unlock() + + return nil +} + +func (s *testStorage) List(ownerID *storage.OwnerID) ([]storage.CID, error) { + s.RLock() + defer s.RUnlock() + + res := make([]storage.CID, 0) + + for cid, cnr := range s.items { + if ownerID == nil || ownerID.Equal(cnr.OwnerID()) { + res = append(res, cid) + } + } + + return res, nil +} + +// New creates new container storage +// that stores containers in go-builtin map. +func New() storage.Storage { + return &testStorage{ + RWMutex: new(sync.RWMutex), + items: make(map[container.ID]*container.Container), + } +} + +// Storage conducts testing of container +// storage for interface specification. +// +// Storage must be empty. +func Storage(t *testing.T, s storage.Storage) { + list, err := s.List(nil) + require.NoError(t, err) + require.Empty(t, list) + + cnr1 := new(container.Container) + cnr1.SetOwnerID(container.OwnerID{1, 2, 3}) + + id1, err := s.Put(cnr1) + require.NoError(t, err) + + res, err := s.Get(*id1) + require.NoError(t, err) + require.Equal(t, cnr1, res) + + cnr2 := new(container.Container) + owner1 := cnr1.OwnerID() + owner1[0]++ + cnr2.SetOwnerID(owner1) + + id2, err := s.Put(cnr2) + require.NoError(t, err) + + res, err = s.Get(*id2) + require.NoError(t, err) + require.Equal(t, cnr2, res) + + list, err = s.List(nil) + require.NoError(t, err) + require.Len(t, list, 2) + require.Contains(t, list, *id1) + require.Contains(t, list, *id2) + + owner1 = cnr1.OwnerID() + list, err = s.List(&owner1) + require.NoError(t, err) + require.Len(t, list, 1) + require.Equal(t, *id1, list[0]) + + owner2 := cnr2.OwnerID() + list, err = s.List(&owner2) + require.NoError(t, err) + require.Len(t, list, 1) + require.Equal(t, *id2, list[0]) +} diff --git a/pkg/core/container/storage/test/storage_test.go b/pkg/core/container/storage/test/storage_test.go new file mode 100644 index 000000000..7614f467f --- /dev/null +++ b/pkg/core/container/storage/test/storage_test.go @@ -0,0 +1,11 @@ +package test + +import ( + "testing" +) + +func TestNewStorage(t *testing.T) { + s := New() + + Storage(t, s) +} diff --git a/pkg/core/netmap/epoch/marshal.go b/pkg/core/netmap/epoch/marshal.go new file mode 100644 index 000000000..a15fc9ca5 --- /dev/null +++ b/pkg/core/netmap/epoch/marshal.go @@ -0,0 +1,37 @@ +package epoch + +import ( + "encoding/binary" + "io" +) + +// Size is a size of Epoch +// in a binary form. +const Size = 8 + +// Marshal encodes Epoch into a +// binary form and returns the result. +// +// Result slice has Size length. +func Marshal(e Epoch) []byte { + d := make([]byte, Size) + + binary.BigEndian.PutUint64(d, ToUint64(e)) + + return d +} + +// UnmarshalBinary unmarshals Epoch from +// a binary representation. +// +// If buffer size is insufficient, +// io.ErrUnexpectedEOF is returned. +func (e *Epoch) UnmarshalBinary(data []byte) error { + if len(data) < Size { + return io.ErrUnexpectedEOF + } + + *e = FromUint64(binary.BigEndian.Uint64(data)) + + return nil +} diff --git a/pkg/core/netmap/epoch/marshal_test.go b/pkg/core/netmap/epoch/marshal_test.go new file mode 100644 index 000000000..7d20ae43e --- /dev/null +++ b/pkg/core/netmap/epoch/marshal_test.go @@ -0,0 +1,20 @@ +package epoch + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEpochMarshal(t *testing.T) { + e := FromUint64(1) + e2 := new(Epoch) + + require.NoError(t, + e2.UnmarshalBinary( + Marshal(e), + ), + ) + + require.True(t, EQ(e, *e2)) +} diff --git a/pkg/core/netmap/epoch/math.go b/pkg/core/netmap/epoch/math.go new file mode 100644 index 000000000..7f68d3c74 --- /dev/null +++ b/pkg/core/netmap/epoch/math.go @@ -0,0 +1,12 @@ +package epoch + +// Sum returns the result of +// summing up two Epoch. +// +// Function defines a binary +// operation of summing two Epoch. +// Try to avoid using operator +// "+" for better portability. +func Sum(a, b Epoch) Epoch { + return FromUint64(ToUint64(a) + ToUint64(b)) +} diff --git a/pkg/core/netmap/epoch/math_test.go b/pkg/core/netmap/epoch/math_test.go new file mode 100644 index 000000000..a1ae88261 --- /dev/null +++ b/pkg/core/netmap/epoch/math_test.go @@ -0,0 +1,28 @@ +package epoch + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEpochMath(t *testing.T) { + items := []struct { + mathFn func(Epoch, Epoch) Epoch + + a, b, c uint64 + }{ + { + mathFn: Sum, a: 1, b: 2, c: 3}, + } + + for _, item := range items { + require.Equal(t, + item.mathFn( + FromUint64(item.a), + FromUint64(item.b), + ), + FromUint64(item.c), + ) + } +} diff --git a/pkg/core/netmap/epoch/relation.go b/pkg/core/netmap/epoch/relation.go new file mode 100644 index 000000000..56ae0a1d0 --- /dev/null +++ b/pkg/core/netmap/epoch/relation.go @@ -0,0 +1,55 @@ +package epoch + +// EQ reports whether e and e2 are the same Epoch. +// +// Function defines the relation of equality +// between two Epoch. Try to avoid comparison through +// "==" operator for better portability. +func EQ(e1, e2 Epoch) bool { + return ToUint64(e1) == ToUint64(e2) +} + +// NE reports whether e1 and e2 are the different Epoch. +// +// Method defines the relation of inequality +// between two Epoch. Try to avoid comparison through +// "!=" operator for better portability. +func NE(e1, e2 Epoch) bool { + return ToUint64(e1) != ToUint64(e2) +} + +// LT reports whether e1 is less Epoch than e2. +// +// Method defines the "less than" relation +// between two Epoch. Try to avoid comparison through +// "<" operator for better portability. +func LT(e1, e2 Epoch) bool { + return ToUint64(e1) < ToUint64(e2) +} + +// GT reports whether e1 is greater Epoch than e2. +// +// Method defines the "greater than" relation +// between two Epoch. Try to avoid comparison through +// ">" operator for better portability. +func GT(e1, e2 Epoch) bool { + return ToUint64(e1) > ToUint64(e2) +} + +// LE reports whether e1 is less or equal Epoch than e2. +// +// Method defines the "less or equal" relation +// between two Epoch. Try to avoid comparison through +// "<=" operator for better portability. +func LE(e1, e2 Epoch) bool { + return ToUint64(e1) <= ToUint64(e2) +} + +// GE reports whether e1 is greater or equal Epoch than e2. +// +// Method defines the "greater or equal" relation +// between two Epoch. Try to avoid comparison through +// ">=" operator for better portability. +func GE(e1, e2 Epoch) bool { + return ToUint64(e1) >= ToUint64(e2) +} diff --git a/pkg/core/netmap/epoch/relation_test.go b/pkg/core/netmap/epoch/relation_test.go new file mode 100644 index 000000000..003cfc1d0 --- /dev/null +++ b/pkg/core/netmap/epoch/relation_test.go @@ -0,0 +1,40 @@ +package epoch + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEpochRelations(t *testing.T) { + items := []struct { + relFn func(Epoch, Epoch) bool + + base, ok, fail uint64 + }{ + {relFn: EQ, base: 1, ok: 1, fail: 2}, + {relFn: NE, base: 1, ok: 2, fail: 1}, + {relFn: LT, base: 1, ok: 2, fail: 0}, + {relFn: GT, base: 1, ok: 0, fail: 2}, + {relFn: LE, base: 1, ok: 1, fail: 0}, + {relFn: LE, base: 1, ok: 2, fail: 0}, + {relFn: GE, base: 1, ok: 0, fail: 2}, + {relFn: GE, base: 1, ok: 1, fail: 2}, + } + + for _, item := range items { + require.True(t, + item.relFn( + FromUint64(item.base), + FromUint64(item.ok), + ), + ) + + require.False(t, + item.relFn( + FromUint64(item.base), + FromUint64(item.fail), + ), + ) + } +} diff --git a/pkg/core/netmap/epoch/type.go b/pkg/core/netmap/epoch/type.go new file mode 100644 index 000000000..79b3da550 --- /dev/null +++ b/pkg/core/netmap/epoch/type.go @@ -0,0 +1,23 @@ +package epoch + +// Epoch represents the +// number of NeoFS epoch. +type Epoch uint64 + +// FromUint64 converts builtin +// uint64 value to Epoch. +// +// Try to avoid direct cast for +// better portability. +func FromUint64(e uint64) Epoch { + return Epoch(e) +} + +// ToUint64 converts Epoch value +// to builtin uint64. +// +// Try to avoid direct cast for +// better portability. +func ToUint64(e Epoch) uint64 { + return uint64(e) +} diff --git a/pkg/core/netmap/netmap.go b/pkg/core/netmap/netmap.go new file mode 100644 index 000000000..17c60834d --- /dev/null +++ b/pkg/core/netmap/netmap.go @@ -0,0 +1,119 @@ +package netmap + +import ( + "bytes" + "sync" + + "github.com/nspcc-dev/neofs-node/pkg/core/netmap/node" + "github.com/nspcc-dev/netmap" +) + +// Info represent node information. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/netmap/node.Info. +type Info = node.Info + +// Bucket represents NeoFS network map as a graph. +// +// If is a type alias of +// github.com/nspcc-dev/netmap.Bucket. +type Bucket = netmap.Bucket + +// NetMap represents NeoFS network map +// with concurrent access support. +type NetMap struct { + mtx *sync.RWMutex + + root *Bucket + + items []Info +} + +// New creates and initializes a new NetMap. +// +// Using the NetMap that has been created with new(NetMap) +// expression (or just declaring a NetMap variable) is unsafe +// and can lead to panic. +func New() *NetMap { + return &NetMap{ + mtx: new(sync.RWMutex), + root: new(Bucket), + } +} + +// Root returns the root bucket of the network map. +// +// Changing the result is unsafe and +// affects the network map. +func (n NetMap) Root() *Bucket { + n.mtx.RLock() + defer n.mtx.RUnlock() + + return n.root +} + +// SetRoot sets the root bucket of the network map. +// +// Subsequent changing the source bucket +// is unsafe and affects the network map. +func (n *NetMap) SetRoot(v *Bucket) { + n.mtx.Lock() + n.root = v + n.mtx.Unlock() +} + +// Nodes returns node list of the network map. +// +// Changing the result is unsafe and +// affects the network map. +func (n NetMap) Nodes() []Info { + n.mtx.RLock() + defer n.mtx.RUnlock() + + return n.items +} + +// SetNodes sets node list of the network map. +// +// Subsequent changing the source slice +// is unsafe and affects the network map. +func (n *NetMap) SetNodes(v []Info) { + n.mtx.Lock() + n.items = v + n.mtx.Unlock() +} + +// AddNode adds node information to the network map +// +// If node with provided information is already presented +// in network map, nothing happens, +func (n *NetMap) AddNode(nodeInfo Info) error { + n.mtx.Lock() + defer n.mtx.Unlock() + + num := -1 + + // looking for existed node info item + for i := range n.items { + if bytes.Equal( + n.items[i].PublicKey(), + nodeInfo.PublicKey(), + ) { + num = i + break + } + } + + // add node if it does not exist + if num < 0 { + n.items = append(n.items, nodeInfo) + num = len(n.items) - 1 + } + + return n.root.AddStrawNode(netmap.Node{ + N: uint32(num), + C: n.items[num].Capacity(), + P: n.items[num].Price(), + }, nodeInfo.Options()...) +} diff --git a/pkg/core/netmap/netmap_test.go b/pkg/core/netmap/netmap_test.go new file mode 100644 index 000000000..e3bf1385c --- /dev/null +++ b/pkg/core/netmap/netmap_test.go @@ -0,0 +1,39 @@ +package netmap + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNetMap_Nodes(t *testing.T) { + nm := New() + + info1 := Info{} + info1.SetPublicKey([]byte{1, 2, 3}) + + info2 := Info{} + info2.SetPublicKey([]byte{4, 5, 6}) + + nodes := []Info{ + info1, + info2, + } + + nm.SetNodes(nodes) + + require.Equal(t, nodes, nm.Nodes()) +} + +func TestNetMap_Root(t *testing.T) { + nm := New() + + bucket := &Bucket{ + Key: "key", + Value: "value", + } + + nm.SetRoot(bucket) + + require.Equal(t, bucket, nm.Root()) +} diff --git a/pkg/core/netmap/node/info.go b/pkg/core/netmap/node/info.go new file mode 100644 index 000000000..e8ca3defc --- /dev/null +++ b/pkg/core/netmap/node/info.go @@ -0,0 +1,154 @@ +package node + +import ( + "errors" +) + +// Info represents the information +// about NeoFS storage node. +type Info struct { + address string // net address + + key []byte // public key + + opts []string // options + + status Status // status bits +} + +// ErrNilInfo is returned by functions that expect +// a non-nil Info pointer, but received nil. +var ErrNilInfo = errors.New("node info is nil") + +// Address returns node network address. +// +// Address format is dictated by +// application architecture. +func (i Info) Address() string { + return i.address +} + +// SetAddress sets node network address. +func (i *Info) SetAddress(v string) { + i.address = v +} + +// Status returns the node status. +func (i Info) Status() Status { + return i.status +} + +// SetStatus sets the node status. +func (i *Info) SetStatus(v Status) { + i.status = v +} + +// PublicKey returns node public key in +// a binary format. +// +// Changing the result is unsafe and +// affects the node info. In order to +// prevent state mutations, use +// CopyPublicKey. +// +// Key format is dictated by +// application architecture. +func (i Info) PublicKey() []byte { + return i.key +} + +// CopyPublicKey returns the copy of +// node public key. +// +// Changing the result is safe and +// does not affect the node info. +func CopyPublicKey(i Info) []byte { + res := make([]byte, len(i.key)) + + copy(res, i.key) + + return res +} + +// SetPublicKey sets node public key +// in a binary format. +// +// Subsequent changing the source slice +// is unsafe and affects node info. +// In order to prevent state mutations, +// use SetPublicKeyCopy. +func (i *Info) SetPublicKey(v []byte) { + i.key = v +} + +// SetPublicKeyCopy copies public key and +// sets the copy as node public key. +// +// Subsequent changing the source slice +// is safe and does not affect node info. +// +// Returns ErrNilInfo on nil node info. +func SetPublicKeyCopy(i *Info, key []byte) error { + if i == nil { + return ErrNilInfo + } + + i.key = make([]byte, len(key)) + + copy(i.key, key) + + return nil +} + +// Options returns node option list. +// +// Changing the result is unsafe and +// affects the node info. In order to +// prevent state mutations, use +// CopyOptions. +// +// Option format is dictated by +// application architecture. +func (i Info) Options() []string { + return i.opts +} + +// CopyOptions returns the copy of +// node options list. +// +// Changing the result is safe and +// does not affect the node info. +func CopyOptions(i Info) []string { + res := make([]string, len(i.opts)) + + copy(res, i.opts) + + return res +} + +// SetOptions sets node option list. +// +// Subsequent changing the source slice +// is unsafe and affects node info. +// In order to prevent state mutations, +// use SetOptionsCopy. +func (i *Info) SetOptions(v []string) { + i.opts = v +} + +// SetOptionsCopy copies option list and sets +// the copy as node options list. +// +// Subsequent changing the source slice +// is safe and does not affect node info. +// +// SetOptionsCopy does nothing if Info is nil. +func SetOptionsCopy(i *Info, opts []string) { + if i == nil { + return + } + + i.opts = make([]string, len(opts)) + + copy(i.opts, opts) +} diff --git a/pkg/core/netmap/node/info_test.go b/pkg/core/netmap/node/info_test.go new file mode 100644 index 000000000..d3a183b37 --- /dev/null +++ b/pkg/core/netmap/node/info_test.go @@ -0,0 +1,133 @@ +package node + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestInfo_Address(t *testing.T) { + i := new(Info) + + addr := "address" + i.SetAddress(addr) + + require.Equal(t, addr, i.Address()) +} + +func TestInfo_Status(t *testing.T) { + i := new(Info) + + st := StatusFromUint64(1) + i.SetStatus(st) + + require.Equal(t, st, i.Status()) +} + +func TestInfo_PublicKey(t *testing.T) { + i := new(Info) + + key := []byte{1, 2, 3} + i.SetPublicKey(key) + + require.Equal(t, key, i.PublicKey()) +} + +func TestCopyPublicKey(t *testing.T) { + i := Info{} + + // set initial node key + initKey := []byte{1, 2, 3} + i.SetPublicKey(initKey) + + // get node key copy + keyCopy := CopyPublicKey(i) + + // change the copy + keyCopy[0]++ + + // check that node key has not changed + require.Equal(t, initKey, i.PublicKey()) +} + +func TestSetPublicKeyCopy(t *testing.T) { + require.EqualError(t, + SetPublicKeyCopy(nil, nil), + ErrNilInfo.Error(), + ) + + i := new(Info) + + // create source key + srcKey := []byte{1, 2, 3} + + // copy and set node key + require.NoError(t, SetPublicKeyCopy(i, srcKey)) + + // get node key + nodeKey := i.PublicKey() + + // change the source key + srcKey[0]++ + + // check that node key has not changed + require.Equal(t, nodeKey, i.PublicKey()) +} + +func TestInfo_Options(t *testing.T) { + i := new(Info) + + opts := []string{ + "opt1", + "opt2", + } + i.SetOptions(opts) + + require.Equal(t, opts, i.Options()) +} + +func TestCopyOptions(t *testing.T) { + i := Info{} + + // set initial node options + initOpts := []string{ + "opt1", + "opt2", + } + i.SetOptions(initOpts) + + // get node options copy + optsCopy := CopyOptions(i) + + // change the copy + optsCopy[0] = "some other opt" + + // check that node options have not changed + require.Equal(t, initOpts, i.Options()) +} + +func TestSetOptionsCopy(t *testing.T) { + require.NotPanics(t, func() { + SetOptionsCopy(nil, nil) + }) + + i := new(Info) + + // create source options + srcOpts := []string{ + "opt1", + "opt2", + } + + // copy and set node options + SetOptionsCopy(i, srcOpts) + + // get node options + nodeOpts := i.Options() + + // change the source options + srcOpts[0] = "some other opt" + + // check that node options have not changed + require.Equal(t, nodeOpts, i.Options()) +} diff --git a/pkg/core/netmap/node/options.go b/pkg/core/netmap/node/options.go new file mode 100644 index 000000000..692c7a7ef --- /dev/null +++ b/pkg/core/netmap/node/options.go @@ -0,0 +1,46 @@ +package node + +import ( + "strconv" + "strings" + + "github.com/nspcc-dev/neofs-api-go/object" +) + +const optionPrice = "/Price:" + +const optionCapacity = "/Capacity:" + +// Price parses node options and returns the price in 1e-8*GAS/Megabyte per month. +// +// User sets the price in GAS/Terabyte per month. +func (i Info) Price() uint64 { + for j := range i.opts { + if strings.HasPrefix(i.opts[j], optionPrice) { + n, err := strconv.ParseFloat(i.opts[j][len(optionPrice):], 64) + if err != nil { + return 0 + } + + return uint64(n*1e8) / uint64(object.UnitsMB) // UnitsMB == megabytes in 1 terabyte + } + } + + return 0 +} + +// Capacity parses node options and returns the capacity . +func (i Info) Capacity() uint64 { + for j := range i.opts { + if strings.HasPrefix(i.opts[j], optionCapacity) { + n, err := strconv.ParseUint(i.opts[j][len(optionCapacity):], 10, 64) + if err != nil { + return 0 + } + + return n + } + } + + return 0 +} diff --git a/pkg/core/netmap/node/options_test.go b/pkg/core/netmap/node/options_test.go new file mode 100644 index 000000000..3965b70e8 --- /dev/null +++ b/pkg/core/netmap/node/options_test.go @@ -0,0 +1,24 @@ +package node + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/object" + "github.com/stretchr/testify/require" +) + +func TestInfo_Price(t *testing.T) { + var info Info + + // too small value + info.opts = []string{"/Price:0.01048575"} + require.Equal(t, uint64(0), info.Price()) + + // min value + info.opts = []string{"/Price:0.01048576"} + require.Equal(t, uint64(1), info.Price()) + + // big value + info.opts = []string{"/Price:1000000000.666"} + require.Equal(t, uint64(1000000000.666*1e8/object.UnitsMB), info.Price()) +} diff --git a/pkg/core/netmap/node/status.go b/pkg/core/netmap/node/status.go new file mode 100644 index 000000000..1f080642d --- /dev/null +++ b/pkg/core/netmap/node/status.go @@ -0,0 +1,63 @@ +package node + +// Status represents a node +// status bits. +type Status uint64 + +const leftBitPos = 64 + +const ( + bitFullStorage = 1 +) + +// returns true if n-th left bit is set (starting at 0). +func isLeftBitSet(value Status, n uint8) bool { + bitMask := Status(1 << (leftBitPos - n)) + return bitMask != 0 && value&bitMask == bitMask +} + +// sets n-th left bit (starting at 0). +func setLeftBit(value *Status, n uint8) { + *value |= Status(1 << (leftBitPos - n)) +} + +// resets n-th left bit (starting at 0). +func resetLeftBit(value *Status, n uint8) { + *value &= ^Status(1 << (leftBitPos - n)) +} + +// Full returns true if node is in Full status. +// +// Full status marks node has enough space +// for storing users objects. +func (n Status) Full() bool { + return isLeftBitSet(n, bitFullStorage) +} + +// SetFull sets Full status of node. +func (n *Status) SetFull() { + setLeftBit(n, bitFullStorage) +} + +// ResetFull resets Full status of node. +func (n *Status) ResetFull() { + resetLeftBit(n, bitFullStorage) +} + +// StatusFromUint64 converts builtin +// uint64 value to Status. +// +// Try to avoid direct cast for +// better portability. +func StatusFromUint64(v uint64) Status { + return Status(v) +} + +// StatusToUint64 converts Status value +// to builtin uint64. +// +// Try to avoid direct cast for +// better portability. +func StatusToUint64(s Status) uint64 { + return uint64(s) +} diff --git a/pkg/core/netmap/node/status_test.go b/pkg/core/netmap/node/status_test.go new file mode 100644 index 000000000..5d540f9e3 --- /dev/null +++ b/pkg/core/netmap/node/status_test.go @@ -0,0 +1,17 @@ +package node + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestStatus_Full(t *testing.T) { + st := new(Status) + + st.SetFull() + require.True(t, st.Full()) + + st.ResetFull() + require.False(t, st.Full()) +} diff --git a/pkg/core/object/extended.go b/pkg/core/object/extended.go new file mode 100644 index 000000000..330169a1b --- /dev/null +++ b/pkg/core/object/extended.go @@ -0,0 +1,94 @@ +package object + +// ExtendedHeaderType represents the enumeration +// of extended header types of the NeoFS object. +type ExtendedHeaderType uint32 + +// ExtendedHeader represents the extended +// header of NeoFS object. +type ExtendedHeader struct { + typ ExtendedHeaderType + + val interface{} +} + +// Type returns the extended header type. +func (h ExtendedHeader) Type() ExtendedHeaderType { + return h.typ +} + +// SetType sets the extended header type. +func (h *ExtendedHeader) SetType(v ExtendedHeaderType) { + h.typ = v +} + +// Value returns the extended header value. +// +// In the case of a reference type, the value is +// returned by reference, so value mutations affect +// header state. Therefore, callers must first copy +// the value before changing manipulations. +func (h ExtendedHeader) Value() interface{} { + return h.val +} + +// SetValue sets the extended header value. +// +// Caller must take into account that each type of +// header usually has a limited set of expected +// value types. +// +// In the case of a reference type, the value is set +// by reference, so source value mutations affect +// header state. Therefore, callers must first copy +// the source value before changing manipulations. +func (h *ExtendedHeader) SetValue(v interface{}) { + h.val = v +} + +// TypeFromUint32 converts builtin +// uint32 value to Epoch. +// +// Try to avoid direct cast for +// better portability. +func TypeFromUint32(v uint32) ExtendedHeaderType { + return ExtendedHeaderType(v) +} + +// TypeToUint32 converts Epoch value +// to builtin uint32. +// +// Try to avoid direct cast for +// better portability. +func TypeToUint32(v ExtendedHeaderType) uint32 { + return uint32(v) +} + +// TypesEQ reports whether t1 and t2 are the same ExtendedHeaderType. +// +// Function defines the relation of equality +// between two ExtendedHeaderType. Try to avoid comparison through +// "==" operator for better portability. +func TypesEQ(t1, t2 ExtendedHeaderType) bool { + return TypeToUint32(t1) == TypeToUint32(t2) +} + +// TypesLT reports whether t1 ExtendedHeaderType +// is less than t2. +// +// Function defines the "less than" relation +// between two ExtendedHeaderType. Try to avoid +// comparison through "<" operator for better portability. +func TypesLT(t1, t2 ExtendedHeaderType) bool { + return TypeToUint32(t1) < TypeToUint32(t2) +} + +// TypesGT reports whether t1 ExtendedHeaderType +// is greater than t2. +// +// Function defines the "greater than" relation +// between two ExtendedHeaderType. Try to avoid +// comparison through ">" operator for better portability. +func TypesGT(t1, t2 ExtendedHeaderType) bool { + return TypeToUint32(t1) > TypeToUint32(t2) +} diff --git a/pkg/core/object/extended_test.go b/pkg/core/object/extended_test.go new file mode 100644 index 000000000..a8bd709ca --- /dev/null +++ b/pkg/core/object/extended_test.go @@ -0,0 +1,25 @@ +package object + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestExtendedHeader_Type(t *testing.T) { + h := new(ExtendedHeader) + + ht := TypeFromUint32(3) + h.SetType(ht) + + require.True(t, TypesEQ(ht, h.Type())) +} + +func TestExtendedHeader_Value(t *testing.T) { + h := new(ExtendedHeader) + + val := 100 + h.SetValue(val) + + require.Equal(t, val, h.Value()) +} diff --git a/pkg/core/object/header.go b/pkg/core/object/header.go new file mode 100644 index 000000000..a55e7683f --- /dev/null +++ b/pkg/core/object/header.go @@ -0,0 +1,73 @@ +package object + +import ( + "errors" +) + +// Header represents NeoFS object header. +type Header struct { + // SystemHeader is an obligatory part of any object header. + // It is used to set the identity and basic parameters of + // the object. + // + // Header must inherit all the methods of SystemHeader, + // so the SystemHeader is embedded in Header. + SystemHeader + + extendedHeaders []ExtendedHeader // extended headers +} + +// ErrNilHeader is returned by functions that expect +// a non-nil Header pointer, but received nil. +var ErrNilHeader = errors.New("object header is nil") + +// ExtendedHeaders returns the extended headers of header. +// +// Changing the result is unsafe and affects the header. +// In order to prevent state mutations, use CopyExtendedHeaders. +func (h *Header) ExtendedHeaders() []ExtendedHeader { + return h.extendedHeaders +} + +// CopyExtendedHeaders returns the copy of extended headers. +// +// Changing the result is safe and does not affect the header. +// +// Returns nil if header is nil. +func CopyExtendedHeaders(h *Header) []ExtendedHeader { + if h == nil { + return nil + } + + res := make([]ExtendedHeader, len(h.extendedHeaders)) + + copy(res, h.extendedHeaders) + + return res +} + +// SetExtendedHeaders sets the extended headers of the header. +// +// Subsequent changing the source slice is unsafe and affects +// the header. In order to prevent state mutations, use +// SetExtendedHeadersCopy. +func (h *Header) SetExtendedHeaders(v []ExtendedHeader) { + h.extendedHeaders = v +} + +// SetExtendedHeadersCopy copies extended headers and sets the copy +// as the object extended headers. +// +// Subsequent changing the source slice is safe and does not affect +// the header. +// +// SetExtendedHeadersCopy does nothing if Header is nil. +func SetExtendedHeadersCopy(h *Header, hs []ExtendedHeader) { + if h == nil { + return + } + + h.extendedHeaders = make([]ExtendedHeader, len(hs)) + + copy(h.extendedHeaders, hs) +} diff --git a/pkg/core/object/header_test.go b/pkg/core/object/header_test.go new file mode 100644 index 000000000..8164bf10b --- /dev/null +++ b/pkg/core/object/header_test.go @@ -0,0 +1,98 @@ +package object + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func testHeaders(num uint32) []ExtendedHeader { + res := make([]ExtendedHeader, num) + + for i := uint32(0); i < num; i++ { + res[i].SetType(TypeFromUint32(i)) + res[i].SetValue(i) + } + + return res +} + +func TestObject_ExtendedHeaders(t *testing.T) { + h := new(Header) + + hs := testHeaders(2) + + h.SetExtendedHeaders(hs) + + require.Equal(t, hs, h.ExtendedHeaders()) +} + +func TestCopyExtendedHeaders(t *testing.T) { + require.Nil(t, CopyExtendedHeaders(nil)) + + h := new(Header) + + // set initial headers + initHs := testHeaders(2) + h.SetExtendedHeaders(initHs) + + // get extended headers copy + hsCopy := CopyExtendedHeaders(h) + + // change the copy + hsCopy[0] = hsCopy[1] + + // check that extended headers have not changed + require.Equal(t, initHs, h.ExtendedHeaders()) +} + +func TestSetExtendedHeadersCopy(t *testing.T) { + require.NotPanics(t, func() { + SetExtendedHeadersCopy(nil, nil) + }) + + h := new(Header) + + // create source headers + srcHs := testHeaders(2) + + // copy and set headers + SetExtendedHeadersCopy(h, srcHs) + + // get extended headers + objHs := h.ExtendedHeaders() + + // change the source headers + srcHs[0] = srcHs[1] + + // check that headeres have not changed + require.Equal(t, objHs, h.ExtendedHeaders()) +} + +func TestHeaderRelations(t *testing.T) { + items := []struct { + relFn func(ExtendedHeaderType, ExtendedHeaderType) bool + + base, ok, fail uint32 + }{ + {relFn: TypesEQ, base: 1, ok: 1, fail: 2}, + {relFn: TypesLT, base: 1, ok: 2, fail: 0}, + {relFn: TypesGT, base: 1, ok: 0, fail: 2}, + } + + for _, item := range items { + require.True(t, + item.relFn( + TypeFromUint32(item.base), + TypeFromUint32(item.ok), + ), + ) + + require.False(t, + item.relFn( + TypeFromUint32(item.base), + TypeFromUint32(item.fail), + ), + ) + } +} diff --git a/pkg/core/object/headers/enum.go b/pkg/core/object/headers/enum.go new file mode 100644 index 000000000..730d35597 --- /dev/null +++ b/pkg/core/object/headers/enum.go @@ -0,0 +1,63 @@ +package headers + +import ( + "github.com/nspcc-dev/neofs-node/pkg/core/object" +) + +// Header represents object extended header. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/object.ExtendedHeader. +type Header = object.ExtendedHeader + +// Type represents extended header type. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/object.ExtendedHeaderType. +type Type = object.ExtendedHeaderType + +const ( + // this is the only place where this cast is appropriate, + // use object.TypeFromUint32 instead. + lowerUndefined = Type(iota) // lower unsupported Type value + + // TypeLink is the type of object reference header. + TypeLink + + // TypeUser is the of user key-value string header. + TypeUser + + // TypeTransform is the type of transformation mark header. + TypeTransform + + // TypeTombstone is the type of tombstone mark header. + TypeTombstone + + // TypeSessionToken is the type of session token header. + TypeSessionToken + + // TypeHomomorphicHash is the type of homomorphic hash header. + TypeHomomorphicHash + + // TypePayloadChecksum is the type of payload checksum header. + TypePayloadChecksum + + // TypeIntegrity is the type of integrity header. + TypeIntegrity + + // TypeStorageGroup is the type of storage group header. + TypeStorageGroup + + // TypePublicKey is the type of public key header. + TypePublicKey + + upperUndefined // upper unsupported Type value +) + +// SupportedType returns true if Type is +// the known type of extended header. Each +// supported type has named constant. +func SupportedType(t Type) bool { + return object.TypesGT(t, lowerUndefined) && + object.TypesLT(t, upperUndefined) +} diff --git a/pkg/core/object/headers/enum_test.go b/pkg/core/object/headers/enum_test.go new file mode 100644 index 000000000..346948ab2 --- /dev/null +++ b/pkg/core/object/headers/enum_test.go @@ -0,0 +1,34 @@ +package headers + +import ( + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/core/object" + "github.com/stretchr/testify/require" +) + +func TestSupportedType(t *testing.T) { + for _, typ := range []Type{ + TypeLink, + TypeUser, + TypeTransform, + TypeTombstone, + TypeSessionToken, + TypeHomomorphicHash, + TypePayloadChecksum, + TypeIntegrity, + TypeStorageGroup, + TypePublicKey, + } { + require.True(t, SupportedType(typ)) + } + + for _, typ := range []Type{ + lowerUndefined, + upperUndefined, + object.TypeFromUint32(object.TypeToUint32(lowerUndefined) - 1), + object.TypeFromUint32(object.TypeToUint32(upperUndefined) + 1), + } { + require.False(t, SupportedType(typ)) + } +} diff --git a/pkg/core/object/headers/user.go b/pkg/core/object/headers/user.go new file mode 100644 index 000000000..9ef738d82 --- /dev/null +++ b/pkg/core/object/headers/user.go @@ -0,0 +1,45 @@ +package headers + +// UserHeader is a value of object extended header +// that carries user string key-value pairs. +// +// All user headers must be type of TypeUser. +// All user header must have UserHeader pointer value. +type UserHeader struct { + key, val string +} + +// NewUserHeader creates, initialized and returns +// the user extended header. +func NewUserHeader(key, val string) *Header { + res := new(Header) + + res.SetType(TypeUser) + + res.SetValue(&UserHeader{ + key: key, + val: val, + }) + + return res +} + +// Key returns the user header key. +func (u UserHeader) Key() string { + return u.key +} + +// SetKey sets the user header key. +func (u *UserHeader) SetKey(key string) { + u.key = key +} + +// Value returns the user header value. +func (u UserHeader) Value() string { + return u.val +} + +// SetValue sets the user header value. +func (u *UserHeader) SetValue(val string) { + u.val = val +} diff --git a/pkg/core/object/headers/user_test.go b/pkg/core/object/headers/user_test.go new file mode 100644 index 000000000..91903ed00 --- /dev/null +++ b/pkg/core/object/headers/user_test.go @@ -0,0 +1,45 @@ +package headers + +import ( + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/core/object" + "github.com/stretchr/testify/require" +) + +func TestUserHeader_Key(t *testing.T) { + h := new(UserHeader) + + key := "random key" + h.SetKey(key) + + require.Equal(t, key, h.Key()) +} + +func TestUserHeader_Value(t *testing.T) { + h := new(UserHeader) + + val := "random value" + h.SetValue(val) + + require.Equal(t, val, h.Value()) +} + +func TestNewUserHeader(t *testing.T) { + key := "user key" + val := "user val" + + h := NewUserHeader(key, val) + + require.True(t, + object.TypesEQ( + TypeUser, + h.Type(), + ), + ) + + uh := h.Value().(*UserHeader) + + require.Equal(t, key, uh.Key()) + require.Equal(t, val, uh.Value()) +} diff --git a/pkg/core/object/id.go b/pkg/core/object/id.go new file mode 100644 index 000000000..c6a5fa582 --- /dev/null +++ b/pkg/core/object/id.go @@ -0,0 +1,61 @@ +package object + +import ( + "github.com/nspcc-dev/neofs-api-go/refs" +) + +// ID represents the object identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/refs.ObjectID. +// FIXME: object ID should be defined in core package. +type ID = refs.ObjectID + +// Address represents NeoFS Object address. +// Acts as a reference to the object. +type Address struct { + cid CID + + id ID +} + +// CID return the identifier of the container +// that the object belongs to. +func (a Address) CID() CID { + return a.cid +} + +// SetCID sets the identifier of the container +// that the object belongs to. +func (a *Address) SetCID(v CID) { + a.cid = v +} + +// ID returns the unique identifier of the +// object in container. +func (a Address) ID() ID { + return a.id +} + +// SetID sets the unique identifier of the +// object in container. +func (a *Address) SetID(v ID) { + a.id = v +} + +// AddressFromObject returns an address based +// on the object's header. +// +// Returns nil on nil object. +func AddressFromObject(o *Object) *Address { + if o == nil { + return nil + } + + a := new(Address) + + a.SetCID(o.CID()) + a.SetID(o.ID()) + + return a +} diff --git a/pkg/core/object/id_test.go b/pkg/core/object/id_test.go new file mode 100644 index 000000000..853c2817e --- /dev/null +++ b/pkg/core/object/id_test.go @@ -0,0 +1,42 @@ +package object + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAddress_CID(t *testing.T) { + a := new(Address) + + cid := CID{1, 2, 3} + a.SetCID(cid) + + require.Equal(t, cid, a.CID()) +} + +func TestAddress_ID(t *testing.T) { + a := new(Address) + + id := ID{1, 2, 3} + a.SetID(id) + + require.Equal(t, id, a.ID()) +} + +func TestAddressFromObject(t *testing.T) { + require.Nil(t, AddressFromObject(nil)) + + o := new(Object) + + cid := CID{4, 5, 6} + o.SetCID(cid) + + id := ID{1, 2, 3} + o.SetID(id) + + a := AddressFromObject(o) + + require.Equal(t, cid, a.CID()) + require.Equal(t, id, a.ID()) +} diff --git a/pkg/core/object/object.go b/pkg/core/object/object.go new file mode 100644 index 000000000..57e874467 --- /dev/null +++ b/pkg/core/object/object.go @@ -0,0 +1,76 @@ +package object + +import ( + "errors" +) + +// Object represents NeoFS Object. +type Object struct { + // Header is an obligatory part of any object. + // It is used to carry any additional information + // besides payload. + // + // Object must inherit all the methods of Header, + // so the Header is embedded in Object. + Header + + payload []byte // payload bytes +} + +// ErrNilObject is returned by functions that expect +// a non-nil Object pointer, but received nil. +var ErrNilObject = errors.New("object is nil") + +// Payload returns payload bytes of the object. +// +// Changing the result is unsafe and affects +// the object. In order to prevent state +// mutations, use CopyPayload. +func (o *Object) Payload() []byte { + return o.payload +} + +// CopyPayload returns the copy of +// object payload. +// +// Changing the result is safe and +// does not affect the object. +// +// CopyPayload returns nil if object is nil. +func CopyPayload(o *Object) []byte { + if o == nil { + return nil + } + + res := make([]byte, len(o.payload)) + copy(res, o.payload) + + return res +} + +// SetPayload sets objecyt payload bytes. +// +// Subsequent changing the source slice +// is unsafe and affects the object. +// In order to prevent state mutations, +// use SetPayloadCopy. +func (o *Object) SetPayload(v []byte) { + o.payload = v +} + +// SetPayloadCopy copies slice bytes and sets +// the copy as object payload. +// +// Subsequent changing the source slice +// is safe and does not affect the object. +// +// SetPayloadCopy does nothing if object is nil. +func SetPayloadCopy(o *Object, payload []byte) { + if o == nil { + return + } + + o.payload = make([]byte, len(payload)) + + copy(o.payload, payload) +} diff --git a/pkg/core/object/object_test.go b/pkg/core/object/object_test.go new file mode 100644 index 000000000..9b1bba54d --- /dev/null +++ b/pkg/core/object/object_test.go @@ -0,0 +1,58 @@ +package object + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestObject_Payload(t *testing.T) { + o := new(Object) + + payload := []byte{1, 2, 3} + o.SetPayload(payload) + + require.Equal(t, payload, o.Payload()) +} + +func TestCopyPayload(t *testing.T) { + require.Nil(t, CopyPayload(nil)) + + o := new(Object) + + // set initial node key + initPayload := []byte{1, 2, 3} + o.SetPayload(initPayload) + + // get payload copy + pCopy := CopyPayload(o) + + // change the copy + pCopy[0]++ + + // check that payload has not changed + require.Equal(t, initPayload, o.Payload()) +} + +func TestSetPayloadCopy(t *testing.T) { + require.NotPanics(t, func() { + SetExtendedHeadersCopy(nil, nil) + }) + + o := new(Object) + + // create source payload + srcPayload := []byte{1, 2, 3} + + // copy and set payload + SetPayloadCopy(o, srcPayload) + + // get payload + objPayload := o.Payload() + + // change the source payload + srcPayload[0]++ + + // check that payload has not changed + require.Equal(t, objPayload, o.Payload()) +} diff --git a/pkg/core/object/storage/storage.go b/pkg/core/object/storage/storage.go new file mode 100644 index 000000000..169059260 --- /dev/null +++ b/pkg/core/object/storage/storage.go @@ -0,0 +1,61 @@ +package storage + +import ( + "errors" + + "github.com/nspcc-dev/neofs-node/pkg/core/object" +) + +// Object represents the NeoFS Object. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/object.Object. +type Object = object.Object + +// Address represents the address of +// NeoFS Object. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/object.Address. +type Address = object.Address + +// Storage is an interface that wraps +// basic object storage methods. +type Storage interface { + // Put saves pointed object to the underlying storage. + // It returns object address for reference and any error + // encountered that caused the saving to interrupt. + // + // Put must return object.ErrNilObject on nil-pointer. + // + // Implementations must not modify the object through the pointer (even temporarily). + // Implementations must not retain the object pointer. + // + // Object rewriting behavior is dictated by implementation. + Put(*Object) (*Address, error) + + // Get reads the object from the storage by address. + // It returns the pointer to requested object and any error encountered. + // + // Get must return exactly one non-nil value. + // Get must return ErrNotFound if the object is not in storage. + // + // Implementations must not retain the object pointer and modify + // the object through it. + Get(Address) (*Object, error) + + // Delete removes the object from the storage. + // It returns any error encountered that caused the deletion to interrupt. + // + // Delete must return nil if object was successfully deleted. + // + // Behavior when deleting a nonexistent object is dictated by implementation. + Delete(Address) error +} + +// ErrNotFound is the error returned when object was not found in storage. +var ErrNotFound = errors.New("object not found") + +// ErrNilStorage is the error returned by functions that +// expect a non-nil object storage implementation, but received nil. +var ErrNilStorage = errors.New("object storage is nil") diff --git a/pkg/core/object/storage/test/storage.go b/pkg/core/object/storage/test/storage.go new file mode 100644 index 000000000..fbea9e777 --- /dev/null +++ b/pkg/core/object/storage/test/storage.go @@ -0,0 +1,88 @@ +package test + +import ( + "sync" + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/core/object" + "github.com/nspcc-dev/neofs-node/pkg/core/object/storage" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +type testStorage struct { + *sync.RWMutex + + items map[storage.Address]*storage.Object +} + +func (s *testStorage) Put(o *storage.Object) (*storage.Address, error) { + if o == nil { + return nil, object.ErrNilObject + } + + a := object.AddressFromObject(o) + + s.Lock() + s.items[*a] = o + s.Unlock() + + return a, nil +} + +func (s *testStorage) Get(a storage.Address) (*storage.Object, error) { + s.RLock() + o, ok := s.items[a] + s.RUnlock() + + if !ok { + return nil, storage.ErrNotFound + } + + return o, nil +} + +func (s *testStorage) Delete(a storage.Address) error { + s.Lock() + delete(s.items, a) + s.Unlock() + + return nil +} + +// New creates new container storage +// that stores containers in go-builtin map. +func New() storage.Storage { + return &testStorage{ + RWMutex: new(sync.RWMutex), + items: make(map[storage.Address]*storage.Object), + } +} + +// Storage conducts testing of object +// storage for interface specification. +// +// Storage must be empty. +func Storage(t *testing.T, s storage.Storage) { + _, err := s.Put(nil) + require.True(t, errors.Is(err, object.ErrNilObject)) + + a := new(object.Address) + _, err = s.Get(*a) + require.True(t, errors.Is(err, storage.ErrNotFound)) + + o := new(object.Object) + o.SetID(object.ID{1, 2, 3}) + + a, err = s.Put(o) + require.NoError(t, err) + + o2, err := s.Get(*a) + require.NoError(t, err) + + require.Equal(t, o, o2) + + require.NoError(t, s.Delete(*a)) + _, err = s.Get(*a) + require.True(t, errors.Is(err, storage.ErrNotFound)) +} diff --git a/pkg/core/object/storage/test/storage_test.go b/pkg/core/object/storage/test/storage_test.go new file mode 100644 index 000000000..7614f467f --- /dev/null +++ b/pkg/core/object/storage/test/storage_test.go @@ -0,0 +1,11 @@ +package test + +import ( + "testing" +) + +func TestNewStorage(t *testing.T) { + s := New() + + Storage(t, s) +} diff --git a/pkg/core/object/sys.go b/pkg/core/object/sys.go new file mode 100644 index 000000000..a73a9ba16 --- /dev/null +++ b/pkg/core/object/sys.go @@ -0,0 +1,107 @@ +package object + +import ( + "github.com/nspcc-dev/neofs-node/pkg/core/container" + "github.com/nspcc-dev/neofs-node/pkg/core/netmap/epoch" +) + +// CID represents the container identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.ID. +type CID = container.ID + +// OwnerID represents the container +// owner identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.OwnerID. +type OwnerID = container.OwnerID + +// Epoch represents the NeoFS epoch number. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/netmap/epoch.Epoch. +type Epoch = epoch.Epoch + +// SystemHeader represents the +// system header of NeoFS Object. +type SystemHeader struct { + version uint64 // object version + + payloadLen uint64 // length of the payload bytes + + id ID // object ID + + cid CID // container ID + + ownerID OwnerID // object owner ID + + creatEpoch Epoch // creation epoch number +} + +// Version returns the object version number. +func (s *SystemHeader) Version() uint64 { + return s.version +} + +// SetVersion sets the object version number. +func (s *SystemHeader) SetVersion(v uint64) { + s.version = v +} + +// PayloadLength returns the length of the +// object payload bytes. +func (s *SystemHeader) PayloadLength() uint64 { + return s.payloadLen +} + +// SetPayloadLength sets the length of the object +// payload bytes. +func (s *SystemHeader) SetPayloadLength(v uint64) { + s.payloadLen = v +} + +// ID returns the object identifier. +func (s *SystemHeader) ID() ID { + return s.id +} + +// SetID sets the object identifier. +func (s *SystemHeader) SetID(v ID) { + s.id = v +} + +// CID returns the container identifier +// to which the object belongs. +func (s *SystemHeader) CID() CID { + return s.cid +} + +// SetCID sets the container identifier +// to which the object belongs. +func (s *SystemHeader) SetCID(v CID) { + s.cid = v +} + +// OwnerID returns the object owner identifier. +func (s *SystemHeader) OwnerID() OwnerID { + return s.ownerID +} + +// SetOwnerID sets the object owner identifier. +func (s *SystemHeader) SetOwnerID(v OwnerID) { + s.ownerID = v +} + +// CreationEpoch returns the epoch number +// in which the object was created. +func (s *SystemHeader) CreationEpoch() Epoch { + return s.creatEpoch +} + +// SetCreationEpoch sets the epoch number +// in which the object was created. +func (s *SystemHeader) SetCreationEpoch(v Epoch) { + s.creatEpoch = v +} diff --git a/pkg/core/object/sys_test.go b/pkg/core/object/sys_test.go new file mode 100644 index 000000000..f14ee82a3 --- /dev/null +++ b/pkg/core/object/sys_test.go @@ -0,0 +1,62 @@ +package object + +import ( + "testing" + + "github.com/nspcc-dev/neofs-node/pkg/core/netmap/epoch" + "github.com/stretchr/testify/require" +) + +func TestSystemHeader_Version(t *testing.T) { + h := new(SystemHeader) + + v := uint64(7) + h.SetVersion(v) + + require.Equal(t, v, h.Version()) +} + +func TestSystemHeader_PayloadLength(t *testing.T) { + h := new(SystemHeader) + + ln := uint64(3) + h.SetPayloadLength(ln) + + require.Equal(t, ln, h.PayloadLength()) +} + +func TestSystemHeader_ID(t *testing.T) { + h := new(SystemHeader) + + id := ID{1, 2, 3} + h.SetID(id) + + require.Equal(t, id, h.ID()) +} + +func TestSystemHeader_CID(t *testing.T) { + h := new(SystemHeader) + + cid := CID{1, 2, 3} + h.SetCID(cid) + + require.Equal(t, cid, h.CID()) +} + +func TestSystemHeader_OwnerID(t *testing.T) { + h := new(SystemHeader) + + ownerID := OwnerID{1, 2, 3} + h.SetOwnerID(ownerID) + + require.Equal(t, ownerID, h.OwnerID()) +} + +func TestSystemHeader_CreationEpoch(t *testing.T) { + h := new(SystemHeader) + + ep := epoch.FromUint64(1) + h.SetCreationEpoch(ep) + + require.True(t, epoch.EQ(ep, h.CreationEpoch())) +} diff --git a/pkg/innerring/bindings.go b/pkg/innerring/bindings.go new file mode 100644 index 000000000..ee0e8652f --- /dev/null +++ b/pkg/innerring/bindings.go @@ -0,0 +1,54 @@ +package innerring + +import ( + "github.com/nspcc-dev/neofs-node/pkg/innerring/timers" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" +) + +type ( + // ContractProcessor interface defines functions for binding event producers + // such as event.Listener and Timers with contract processor. + ContractProcessor interface { + ListenerParsers() []event.ParserInfo + ListenerHandlers() []event.HandlerInfo + TimersHandlers() []event.HandlerInfo + } +) + +func connectListenerWithProcessor(l event.Listener, p ContractProcessor) { + // register parsers + for _, parser := range p.ListenerParsers() { + l.SetParser(parser) + } + + // register handlers + for _, handler := range p.ListenerHandlers() { + l.RegisterHandler(handler) + } +} + +func connectTimerWithProcessor(t *timers.Timers, p ContractProcessor) error { + var err error + for _, parser := range p.TimersHandlers() { + err = t.RegisterHandler(parser) + if err != nil { + return err + } + } + + return nil +} + +// bindMorphProcessor connects both morph chain listener handlers and +// local timers handlers. +func bindMorphProcessor(proc ContractProcessor, s *Server) error { + connectListenerWithProcessor(s.morphListener, proc) + return connectTimerWithProcessor(s.localTimers, proc) +} + +// bindMainnetProcessor connects both mainnet chain listener handlers and +// local timers handlers. +func bindMainnetProcessor(proc ContractProcessor, s *Server) error { + connectListenerWithProcessor(s.mainnetListener, proc) + return connectTimerWithProcessor(s.localTimers, proc) +} diff --git a/pkg/innerring/innerring.go b/pkg/innerring/innerring.go new file mode 100644 index 000000000..0b486b90c --- /dev/null +++ b/pkg/innerring/innerring.go @@ -0,0 +1,302 @@ +package innerring + +import ( + "context" + "crypto/ecdsa" + + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/util" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-node/pkg/innerring/invoke" + "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/balance" + "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/neofs" + "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap" + "github.com/nspcc-dev/neofs-node/pkg/innerring/timers" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/nspcc-dev/neofs-node/pkg/morph/subscriber" + "github.com/pkg/errors" + "github.com/spf13/viper" + "go.uber.org/atomic" + "go.uber.org/zap" +) + +type ( + // Server is the inner ring application structure, that contains all event + // processors, shared variables and event handlers. + Server struct { + log *zap.Logger + + // event producers + morphListener event.Listener + mainnetListener event.Listener + localTimers *timers.Timers + + // global state + morphClient *client.Client + mainnetClient *client.Client + epochCounter atomic.Uint64 + activeState atomic.Bool + + // todo: export error channel + } + + contracts struct { + neofs util.Uint160 // in mainnet + netmap util.Uint160 // in morph + balance util.Uint160 // in morph + container util.Uint160 // in morph + audit util.Uint160 // in morph + reputation util.Uint160 // in morph + neofsid util.Uint160 // in morph + gas util.Uint160 // native contract in both chains + } + + chainParams struct { + log *zap.Logger + cfg *viper.Viper + key *ecdsa.PrivateKey + name string + gas util.Uint160 + } +) + +const ( + morphPrefix = "morph" + mainnetPrefix = "mainnet" +) + +// Start runs all event providers. +func (s *Server) Start(ctx context.Context) error { + s.localTimers.Start(ctx) // local timers start ticking + + go s.morphListener.Listen(ctx) // listen for neo:morph events + go s.mainnetListener.Listen(ctx) // listen for neo:mainnet events + + return nil +} + +// Stop closes all subscription channels. +func (s *Server) Stop() { + go s.morphListener.Stop() + go s.mainnetListener.Stop() +} + +// New creates instance of inner ring sever structure. +func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper) (*Server, error) { + server := &Server{log: log} + + // prepare inner ring node private key + key, err := crypto.LoadPrivateKey(cfg.GetString("key")) + if err != nil { + return nil, errors.Wrap(err, "ir: can't create private key") + } + + // get all script hashes of contracts + contracts, err := parseContracts(cfg) + if err != nil { + return nil, err + } + + // create local timer instance + server.localTimers = timers.New(&timers.Params{ + Log: log, + EpochDuration: cfg.GetDuration("timers.epoch"), + }) + + morphChain := &chainParams{ + log: log, + cfg: cfg, + key: key, + gas: contracts.gas, + name: morphPrefix, + } + + // create morph listener + server.morphListener, err = createListener(ctx, morphChain) + if err != nil { + return nil, err + } + + // create morph client + server.morphClient, err = createClient(ctx, morphChain) + if err != nil { + return nil, err + } + + mainnetChain := morphChain + mainnetChain.name = mainnetPrefix + + // create mainnet listener + server.mainnetListener, err = createListener(ctx, mainnetChain) + if err != nil { + return nil, err + } + + // create mainnet client + server.mainnetClient, err = createClient(ctx, mainnetChain) + if err != nil { + return nil, err + } + + // create netmap processor + netmapProcessor, err := netmap.New(&netmap.Params{ + Log: log, + PoolSize: cfg.GetInt("workers.netmap"), + NetmapContract: contracts.netmap, + EpochTimer: server.localTimers, + MorphClient: server.morphClient, + EpochState: server, + ActiveState: server, + }) + if err != nil { + return nil, err + } + + err = bindMorphProcessor(netmapProcessor, server) + if err != nil { + return nil, err + } + + // todo: create container processor + + // create balance processor + balanceProcessor, err := balance.New(&balance.Params{ + Log: log, + PoolSize: cfg.GetInt("workers.balance"), + NeoFSContract: contracts.neofs, + BalanceContract: contracts.balance, + MainnetClient: server.mainnetClient, + ActiveState: server, + }) + if err != nil { + return nil, err + } + + err = bindMorphProcessor(balanceProcessor, server) + if err != nil { + return nil, err + } + + // todo: create reputation processor + + // create mainnnet neofs processor + neofsProcessor, err := neofs.New(&neofs.Params{ + Log: log, + PoolSize: cfg.GetInt("workers.neofs"), + NeoFSContract: contracts.neofs, + BalanceContract: contracts.balance, + MorphClient: server.morphClient, + EpochState: server, + ActiveState: server, + }) + if err != nil { + return nil, err + } + + err = bindMainnetProcessor(neofsProcessor, server) + if err != nil { + return nil, err + } + + // todo: create vivid id component + // todo: create audit scheduler + + err = initConfigFromBlockchain(server, contracts, &key.PublicKey) + if err != nil { + return nil, err + } + + return server, nil +} + +func createListener(ctx context.Context, p *chainParams) (event.Listener, error) { + sub, err := subscriber.New(ctx, &subscriber.Params{ + Log: p.log, + Endpoint: p.cfg.GetString(p.name + ".endpoint.notification"), + DialTimeout: p.cfg.GetDuration(p.name + ".dial_timeouts"), + }) + if err != nil { + return nil, err + } + + listener, err := event.NewListener(event.ListenerParams{ + Logger: p.log, + Subscriber: sub, + }) + if err != nil { + return nil, err + } + + return listener, err +} + +func createClient(ctx context.Context, p *chainParams) (*client.Client, error) { + return client.New( + p.key, + p.cfg.GetString(p.name+".endpoint.client"), + client.WithContext(ctx), + client.WithLogger(p.log), + client.WithDialTimeout(p.cfg.GetDuration(p.name+".dial_timeouts")), + client.WithMagic(netmode.Magic(p.cfg.GetUint32(p.name+".magic_number"))), + client.WithGasContract(p.gas), + ) +} + +func parseContracts(cfg *viper.Viper) (*contracts, error) { + var ( + result = new(contracts) + err error + ) + + netmapContractStr := cfg.GetString("contracts.netmap") + neofsContractStr := cfg.GetString("contracts.neofs") + balanceContractStr := cfg.GetString("contracts.balance") + nativeGasContractStr := cfg.GetString("contracts.gas") + + result.netmap, err = util.Uint160DecodeStringLE(netmapContractStr) + if err != nil { + return nil, errors.Wrap(err, "ir: can't read netmap script-hash") + } + + result.neofs, err = util.Uint160DecodeStringLE(neofsContractStr) + if err != nil { + return nil, errors.Wrap(err, "ir: can't read neofs script-hash") + } + + result.balance, err = util.Uint160DecodeStringLE(balanceContractStr) + if err != nil { + return nil, errors.Wrap(err, "ir: can't read balance script-hash") + } + + result.gas, err = util.Uint160DecodeStringLE(nativeGasContractStr) + if err != nil { + return nil, errors.Wrap(err, "ir: can't read native gas script-hash") + } + + return result, nil +} + +func initConfigFromBlockchain(s *Server, c *contracts, key *ecdsa.PublicKey) error { + // get current epoch + epoch, err := invoke.Epoch(s.morphClient, c.netmap) + if err != nil { + return err + } + + // check if node inside inner ring list + state, err := invoke.IsInnerRing(s.mainnetClient, c.neofs, key) + if err != nil { + return err + } + + s.epochCounter.Store(uint64(epoch)) + s.activeState.Store(state) + + s.log.Debug("read config from blockchain", + zap.Bool("active", state), + zap.Int64("epoch", epoch), + ) + + return nil +} diff --git a/pkg/innerring/invoke/balance.go b/pkg/innerring/invoke/balance.go new file mode 100644 index 000000000..9dc30da9f --- /dev/null +++ b/pkg/innerring/invoke/balance.go @@ -0,0 +1,59 @@ +package invoke + +import ( + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" +) + +type ( + // TransferXParams for TransferBalanceX invocation. + TransferXParams struct { + Sender []byte + Receiver []byte + Amount int64 // in Fixed16 + Comment []byte + } + + // LockParams for LockAsset invocation. + LockParams struct { + ID []byte + User util.Uint160 + LockAccount util.Uint160 + Amount int64 // in Fixed16 + Until uint64 // epochs + } +) + +const ( + transferXMethod = "transferX" + lockMethod = "Lock" +) + +// TransferBalanceX invokes transferX method. +func TransferBalanceX(cli *client.Client, con util.Uint160, p *TransferXParams) error { + if cli == nil { + return client.ErrNilClient + } + + return cli.Invoke(con, extraFee, transferXMethod, + p.Sender, + p.Receiver, + p.Amount, + p.Comment, + ) +} + +// LockAsset invokes Lock method. +func LockAsset(cli *client.Client, con util.Uint160, p *LockParams) error { + if cli == nil { + return client.ErrNilClient + } + + return cli.Invoke(con, extraFee, lockMethod, + p.ID, + p.User.BytesBE(), + p.LockAccount.BytesBE(), + p.Amount, + int64(p.Until), // fixme: invoke can work only with int64 values + ) +} diff --git a/pkg/innerring/invoke/neofs.go b/pkg/innerring/invoke/neofs.go new file mode 100644 index 000000000..e7df4151c --- /dev/null +++ b/pkg/innerring/invoke/neofs.go @@ -0,0 +1,68 @@ +package invoke + +import ( + "crypto/ecdsa" + + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" +) + +type ( + // ChequeParams for CashOutCheque invocation. + ChequeParams struct { + ID []byte + Amount int64 // Fixed8 + User util.Uint160 + LockAccount util.Uint160 + } +) + +const ( + // Extra SysFee for contract invocations. Contracts execute inner ring + // invocations in two steps: collection and execution. At collection + // step contract waits for (2\3*n + 1) invocations, and then in execution + // stage contract actually makes changes in the contract storage. SysFee + // for invocation calculated based on testinvoke which happens at collection + // stage. Therefore client has to provide some extra SysFee to operate at + // execution stage. Otherwise invocation will fail due to gas limit. + extraFee = 5000_0000 // 0.5 Fixed8 gas + + checkIsInnerRingMethod = "IsInnerRing" + chequeMethod = "Cheque" +) + +// IsInnerRing returns true if 'key' is presented in inner ring list. +func IsInnerRing(cli *client.Client, con util.Uint160, key *ecdsa.PublicKey) (bool, error) { + if cli == nil { + return false, client.ErrNilClient + } + + pubKey := crypto.MarshalPublicKey(key) + + val, err := cli.TestInvoke(con, checkIsInnerRingMethod, pubKey) + if err != nil { + return false, err + } + + isInnerRing, err := client.BoolFromStackParameter(val[0]) + if err != nil { + return false, err + } + + return isInnerRing, nil +} + +// CashOutCheque invokes Cheque method. +func CashOutCheque(cli *client.Client, con util.Uint160, p *ChequeParams) error { + if cli == nil { + return client.ErrNilClient + } + + return cli.Invoke(con, extraFee, chequeMethod, + p.ID, + p.User.BytesBE(), + p.Amount, + p.LockAccount.BytesBE(), + ) +} diff --git a/pkg/innerring/invoke/netmap.go b/pkg/innerring/invoke/netmap.go new file mode 100644 index 000000000..7bfd34759 --- /dev/null +++ b/pkg/innerring/invoke/netmap.go @@ -0,0 +1,39 @@ +package invoke + +import ( + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" +) + +const ( + getEpochMethod = "Epoch" + setNewEpochMethod = "NewEpoch" +) + +// Epoch return epoch value from contract. +func Epoch(cli *client.Client, con util.Uint160) (int64, error) { + if cli == nil { + return 0, client.ErrNilClient + } + + val, err := cli.TestInvoke(con, getEpochMethod) + if err != nil { + return 0, err + } + + epoch, err := client.IntFromStackParameter(val[0]) + if err != nil { + return 0, err + } + + return epoch, nil +} + +// SetNewEpoch invokes NewEpoch method. +func SetNewEpoch(cli *client.Client, con util.Uint160, epoch uint64) error { + if cli == nil { + return client.ErrNilClient + } + + return cli.Invoke(con, extraFee, setNewEpochMethod, int64(epoch)) +} diff --git a/pkg/innerring/processors/balance/handlers.go b/pkg/innerring/processors/balance/handlers.go new file mode 100644 index 000000000..8aa8ed9dc --- /dev/null +++ b/pkg/innerring/processors/balance/handlers.go @@ -0,0 +1,25 @@ +package balance + +import ( + "encoding/hex" + + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + balanceEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/balance" + "go.uber.org/zap" +) + +func (bp *Processor) handleLock(ev event.Event) { + lock := ev.(balanceEvent.Lock) // todo: check panic in production + bp.log.Info("notification", + zap.String("type", "lock"), + zap.String("value", hex.EncodeToString(lock.ID()))) + + // send event to the worker pool + + err := bp.pool.Submit(func() { bp.processLock(&lock) }) + if err != nil { + // todo: move into controlled degradation stage + bp.log.Warn("balance worker pool drained", + zap.Int("capacity", bp.pool.Cap())) + } +} diff --git a/pkg/innerring/processors/balance/process_assets.go b/pkg/innerring/processors/balance/process_assets.go new file mode 100644 index 000000000..acb180391 --- /dev/null +++ b/pkg/innerring/processors/balance/process_assets.go @@ -0,0 +1,27 @@ +package balance + +import ( + "github.com/nspcc-dev/neofs-node/pkg/innerring/invoke" + balanceEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/balance" + "go.uber.org/zap" +) + +// Process lock event by invoking Cheque method in main net to send assets +// back to the withdraw issuer. +func (bp *Processor) processLock(lock *balanceEvent.Lock) { + if !bp.activeState.IsActive() { + bp.log.Info("passive mode, ignore balance lock") + return + } + + err := invoke.CashOutCheque(bp.mainnetClient, bp.neofsContract, + &invoke.ChequeParams{ + ID: lock.ID(), + Amount: lock.Amount() / 1_0000_0000, // todo: Fixed16 to Fixed8 + User: lock.User(), + LockAccount: lock.LockAccount(), + }) + if err != nil { + bp.log.Error("can't send lock asset tx", zap.Error(err)) + } +} diff --git a/pkg/innerring/processors/balance/processor.go b/pkg/innerring/processors/balance/processor.go new file mode 100644 index 000000000..d317aff64 --- /dev/null +++ b/pkg/innerring/processors/balance/processor.go @@ -0,0 +1,103 @@ +package balance + +import ( + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + balanceEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/balance" + "github.com/panjf2000/ants/v2" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +type ( + // ActiveState is a callback interface for inner ring global state + ActiveState interface { + IsActive() bool + } + + // Processor of events produced by balance contract in morph chain. + Processor struct { + log *zap.Logger + pool *ants.Pool + neofsContract util.Uint160 + balanceContract util.Uint160 + mainnetClient *client.Client + activeState ActiveState + } + + // Params of the processor constructor. + Params struct { + Log *zap.Logger + PoolSize int + NeoFSContract util.Uint160 + BalanceContract util.Uint160 + MainnetClient *client.Client + ActiveState ActiveState + } +) + +const ( + lockNotification = "Lock" +) + +// New creates balance contract processor instance. +func New(p *Params) (*Processor, error) { + switch { + case p.Log == nil: + return nil, errors.New("ir/balance: logger is not set") + case p.MainnetClient == nil: + return nil, errors.New("ir/balance: neo:mainnet client is not set") + case p.ActiveState == nil: + return nil, errors.New("ir/balance: global state is not set") + } + + p.Log.Debug("balance worker pool", zap.Int("size", p.PoolSize)) + + pool, err := ants.NewPool(p.PoolSize, ants.WithNonblocking(true)) + if err != nil { + return nil, errors.Wrap(err, "ir/balance: can't create worker pool") + } + + return &Processor{ + log: p.Log, + pool: pool, + neofsContract: p.NeoFSContract, + balanceContract: p.BalanceContract, + mainnetClient: p.MainnetClient, + activeState: p.ActiveState, + }, nil +} + +// ListenerParsers for the 'event.Listener' event producer. +func (bp *Processor) ListenerParsers() []event.ParserInfo { + var parsers []event.ParserInfo + + // new lock event + lock := event.ParserInfo{} + lock.SetType(lockNotification) + lock.SetScriptHash(bp.balanceContract) + lock.SetParser(balanceEvent.ParseLock) + parsers = append(parsers, lock) + + return parsers +} + +// ListenerHandlers for the 'event.Listener' event producer. +func (bp *Processor) ListenerHandlers() []event.HandlerInfo { + var handlers []event.HandlerInfo + + // lock handler + lock := event.HandlerInfo{} + lock.SetType(lockNotification) + lock.SetScriptHash(bp.balanceContract) + lock.SetHandler(bp.handleLock) + handlers = append(handlers, lock) + + return handlers +} + +// TimersHandlers for the 'Timers' event producer. +func (bp *Processor) TimersHandlers() []event.HandlerInfo { + return nil +} diff --git a/pkg/innerring/processors/neofs/handlers.go b/pkg/innerring/processors/neofs/handlers.go new file mode 100644 index 000000000..a64f6e771 --- /dev/null +++ b/pkg/innerring/processors/neofs/handlers.go @@ -0,0 +1,57 @@ +package neofs + +import ( + "encoding/hex" + + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + neofsEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/neofs" + "go.uber.org/zap" +) + +func (np *Processor) handleDeposit(ev event.Event) { + deposit := ev.(neofsEvent.Deposit) // todo: check panic in production + np.log.Info("notification", + zap.String("type", "deposit"), + zap.String("id", hex.EncodeToString(deposit.ID()))) + + // send event to the worker pool + + err := np.pool.Submit(func() { np.processDeposit(&deposit) }) + if err != nil { + // todo: move into controlled degradation stage + np.log.Warn("neofs processor worker pool drained", + zap.Int("capacity", np.pool.Cap())) + } +} + +func (np *Processor) handleWithdraw(ev event.Event) { + withdraw := ev.(neofsEvent.Withdraw) // todo: check panic in production + np.log.Info("notification", + zap.String("type", "withdraw"), + zap.String("id", hex.EncodeToString(withdraw.ID()))) + + // send event to the worker pool + + err := np.pool.Submit(func() { np.processWithdraw(&withdraw) }) + if err != nil { + // todo: move into controlled degradation stage + np.log.Warn("neofs processor worker pool drained", + zap.Int("capacity", np.pool.Cap())) + } +} + +func (np *Processor) handleCheque(ev event.Event) { + cheque := ev.(neofsEvent.Cheque) // todo: check panic in production + np.log.Info("notification", + zap.String("type", "cheque"), + zap.String("id", hex.EncodeToString(cheque.ID()))) + + // send event to the worker pool + + err := np.pool.Submit(func() { np.processCheque(&cheque) }) + if err != nil { + // todo: move into controlled degradation stage + np.log.Warn("neofs processor worker pool drained", + zap.Int("capacity", np.pool.Cap())) + } +} diff --git a/pkg/innerring/processors/neofs/process_assets.go b/pkg/innerring/processors/neofs/process_assets.go new file mode 100644 index 000000000..5a126604d --- /dev/null +++ b/pkg/innerring/processors/neofs/process_assets.go @@ -0,0 +1,104 @@ +package neofs + +import ( + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/innerring/invoke" + neofsEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/neofs" + "go.uber.org/zap" +) + +const ( + // txLogPrefix used for balance transfer comments in balance contract. + txLogPrefix = "mainnet:" + + // lockAccountLifeTime defines amount of epochs when lock account is valid. + lockAccountLifetime uint64 = 20 +) + +var ( + // fedReserveAddr is a special account in balance contract. + fedReserveAddr = []byte{0x0F, 0xED} +) + +// Process deposit event by invoking balance contract and sending native +// gas in morph chain. +func (np *Processor) processDeposit(deposit *neofsEvent.Deposit) { + if !np.activeState.IsActive() { + np.log.Info("passive mode, ignore deposit") + return + } + + // send transferX to balance contract + err := invoke.TransferBalanceX(np.morphClient, np.balanceContract, + &invoke.TransferXParams{ + Sender: fedReserveAddr, + Receiver: deposit.To().BytesBE(), + Amount: deposit.Amount() * 1_0000_0000, // from Fixed8 to Fixed16 + Comment: append([]byte(txLogPrefix), deposit.ID()...), + }) + if err != nil { + np.log.Error("can't transfer assets to balance contract", zap.Error(err)) + } + + // fixme: send gas when ^ tx accepted + // fixme: do not send gas to the same user twice per epoch + err = np.morphClient.TransferGas(deposit.To(), util.Fixed8FromInt64(2)) + if err != nil { + np.log.Error("can't transfer native gas to receiver", zap.Error(err)) + } +} + +// Process withdraw event by locking assets in balance account. +func (np *Processor) processWithdraw(withdraw *neofsEvent.Withdraw) { + if !np.activeState.IsActive() { + np.log.Info("passive mode, ignore withdraw") + return + } + + if len(withdraw.ID()) < util.Uint160Size { + np.log.Error("tx id size is less than script hash size") + return + } + + // create lock account + // todo: check collision there, consider reversed script hash + lock, err := util.Uint160DecodeBytesBE(withdraw.ID()[:util.Uint160Size]) + if err != nil { + np.log.Error("can't create lock account", zap.Error(err)) + return + } + + curEpoch := np.epochState.EpochCounter() + + err = invoke.LockAsset(np.morphClient, np.balanceContract, + &invoke.LockParams{ + ID: withdraw.ID(), + User: withdraw.User(), + LockAccount: lock, + Amount: withdraw.Amount() * 1_0000_0000, // todo: from Fixed8 to Fixed16 + Until: curEpoch + lockAccountLifetime, + }) + if err != nil { + np.log.Error("can't lock assets for withdraw", zap.Error(err)) + } +} + +// Process cheque event by transferring assets from lock account back to +// reserve account. +func (np *Processor) processCheque(cheque *neofsEvent.Cheque) { + if !np.activeState.IsActive() { + np.log.Info("passive mode, ignore cheque") + return + } + + err := invoke.TransferBalanceX(np.morphClient, np.balanceContract, + &invoke.TransferXParams{ + Sender: cheque.LockAccount().BytesBE(), + Receiver: fedReserveAddr, + Amount: cheque.Amount() * 1_0000_0000, // from Fixed8 to Fixed16 + Comment: append([]byte(txLogPrefix), cheque.ID()...), + }) + if err != nil { + np.log.Error("can't transfer assets to fed contract", zap.Error(err)) + } +} diff --git a/pkg/innerring/processors/neofs/processor.go b/pkg/innerring/processors/neofs/processor.go new file mode 100644 index 000000000..6a60f5204 --- /dev/null +++ b/pkg/innerring/processors/neofs/processor.go @@ -0,0 +1,143 @@ +package neofs + +import ( + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + neofsEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/neofs" + "github.com/panjf2000/ants/v2" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +type ( + // EpochState is a callback interface for inner ring global state + EpochState interface { + EpochCounter() uint64 + } + + // ActiveState is a callback interface for inner ring global state + ActiveState interface { + IsActive() bool + } + + // Processor of events produced by neofs contract in main net. + Processor struct { + log *zap.Logger + pool *ants.Pool + neofsContract util.Uint160 + balanceContract util.Uint160 + morphClient *client.Client + epochState EpochState + activeState ActiveState + } + + // Params of the processor constructor. + Params struct { + Log *zap.Logger + PoolSize int + NeoFSContract util.Uint160 + BalanceContract util.Uint160 + MorphClient *client.Client + EpochState EpochState + ActiveState ActiveState + } +) + +const ( + depositNotification = "Deposit" + withdrawNotification = "Withdraw" + chequeNotification = "Cheque" +) + +// New creates neofs mainnet contract processor instance. +func New(p *Params) (*Processor, error) { + switch { + case p.Log == nil: + return nil, errors.New("ir/neofs: logger is not set") + case p.MorphClient == nil: + return nil, errors.New("ir/neofs: neo:morph client is not set") + case p.EpochState == nil: + return nil, errors.New("ir/neofs: global state is not set") + case p.ActiveState == nil: + return nil, errors.New("ir/neofs: global state is not set") + } + + p.Log.Debug("neofs worker pool", zap.Int("size", p.PoolSize)) + + pool, err := ants.NewPool(p.PoolSize, ants.WithNonblocking(true)) + if err != nil { + return nil, errors.Wrap(err, "ir/neofs: can't create worker pool") + } + + return &Processor{ + log: p.Log, + pool: pool, + neofsContract: p.NeoFSContract, + balanceContract: p.BalanceContract, + morphClient: p.MorphClient, + epochState: p.EpochState, + activeState: p.ActiveState, + }, nil +} + +// ListenerParsers for the 'event.Listener' event producer. +func (np *Processor) ListenerParsers() []event.ParserInfo { + var parsers []event.ParserInfo + + // deposit event + deposit := event.ParserInfo{} + deposit.SetType(depositNotification) + deposit.SetScriptHash(np.neofsContract) + deposit.SetParser(neofsEvent.ParseDeposit) + parsers = append(parsers, deposit) + + // withdraw event + withdraw := event.ParserInfo{} + withdraw.SetType(withdrawNotification) + withdraw.SetScriptHash(np.neofsContract) + withdraw.SetParser(neofsEvent.ParseWithdraw) + parsers = append(parsers, withdraw) + + // cheque event + cheque := event.ParserInfo{} + cheque.SetType(chequeNotification) + cheque.SetScriptHash(np.neofsContract) + cheque.SetParser(neofsEvent.ParseCheque) + parsers = append(parsers, cheque) + + return parsers +} + +// ListenerHandlers for the 'event.Listener' event producer. +func (np *Processor) ListenerHandlers() []event.HandlerInfo { + var handlers []event.HandlerInfo + + // deposit handler + deposit := event.HandlerInfo{} + deposit.SetType(depositNotification) + deposit.SetScriptHash(np.neofsContract) + deposit.SetHandler(np.handleDeposit) + handlers = append(handlers, deposit) + + // withdraw handler + withdraw := event.HandlerInfo{} + withdraw.SetType(withdrawNotification) + withdraw.SetScriptHash(np.neofsContract) + withdraw.SetHandler(np.handleWithdraw) + handlers = append(handlers, withdraw) + + // cheque handler + cheque := event.HandlerInfo{} + cheque.SetType(chequeNotification) + cheque.SetScriptHash(np.neofsContract) + cheque.SetHandler(np.handleCheque) + handlers = append(handlers, cheque) + + return handlers +} + +// TimersHandlers for the 'Timers' event producer. +func (np *Processor) TimersHandlers() []event.HandlerInfo { + return nil +} diff --git a/pkg/innerring/processors/netmap/handlers.go b/pkg/innerring/processors/netmap/handlers.go new file mode 100644 index 000000000..b403d8c7d --- /dev/null +++ b/pkg/innerring/processors/netmap/handlers.go @@ -0,0 +1,40 @@ +package netmap + +import ( + timerEvent "github.com/nspcc-dev/neofs-node/pkg/innerring/timers" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + netmapEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/netmap" + "go.uber.org/zap" +) + +func (np *Processor) handleNewEpochTick(ev event.Event) { + _ = ev.(timerEvent.NewEpochTick) // todo: check panic in production + np.log.Info("tick", zap.String("type", "epoch")) + + // send event to the worker pool + + err := np.pool.Submit(func() { np.processNewEpochTick() }) + if err != nil { + // todo: move into controlled degradation stage + np.log.Warn("netmap worker pool drained", + zap.Int("capacity", np.pool.Cap())) + } +} + +func (np *Processor) handleNewEpoch(ev event.Event) { + epochEvent := ev.(netmapEvent.NewEpoch) // todo: check panic in production + np.log.Info("notification", + zap.String("type", "new epoch"), + zap.Uint64("value", epochEvent.EpochNumber())) + + // send event to the worker pool + + err := np.pool.Submit(func() { + np.processNewEpoch(epochEvent.EpochNumber()) + }) + if err != nil { + // todo: move into controlled degradation stage + np.log.Warn("netmap worker pool drained", + zap.Int("capacity", np.pool.Cap())) + } +} diff --git a/pkg/innerring/processors/netmap/process_epoch.go b/pkg/innerring/processors/netmap/process_epoch.go new file mode 100644 index 000000000..abafd8000 --- /dev/null +++ b/pkg/innerring/processors/netmap/process_epoch.go @@ -0,0 +1,29 @@ +package netmap + +import ( + "github.com/nspcc-dev/neofs-node/pkg/innerring/invoke" + "go.uber.org/zap" +) + +// Process new epoch notification by setting global epoch value and resetting +// local epoch timer. +func (np *Processor) processNewEpoch(epoch uint64) { + np.epochState.SetEpochCounter(epoch) + np.epochTimer.ResetEpochTimer() +} + +// Process new epoch tick by invoking new epoch method in network map contract. +func (np *Processor) processNewEpochTick() { + if !np.activeState.IsActive() { + np.log.Info("passive mode, ignore new epoch tick") + return + } + + nextEpoch := np.epochState.EpochCounter() + 1 + np.log.Debug("next epoch", zap.Uint64("value", nextEpoch)) + + err := invoke.SetNewEpoch(np.morphClient, np.netmapContract, nextEpoch) + if err != nil { + np.log.Error("can't invoke netmap.NewEpoch", zap.Error(err)) + } +} diff --git a/pkg/innerring/processors/netmap/processor.go b/pkg/innerring/processors/netmap/processor.go new file mode 100644 index 000000000..d762c7b04 --- /dev/null +++ b/pkg/innerring/processors/netmap/processor.go @@ -0,0 +1,131 @@ +package netmap + +import ( + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/innerring/timers" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + netmapEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/netmap" + "github.com/panjf2000/ants/v2" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +type ( + // EpochTimerReseter is a callback interface for tickers component. + EpochTimerReseter interface { + ResetEpochTimer() + } + + // EpochState is a callback interface for inner ring global state. + EpochState interface { + SetEpochCounter(uint64) + EpochCounter() uint64 + } + + // ActiveState is a callback interface for inner ring global state. + ActiveState interface { + IsActive() bool + } + + // Processor of events produced by network map contract + // and new epoch ticker, because it is related to contract. + Processor struct { + log *zap.Logger + pool *ants.Pool + netmapContract util.Uint160 + epochTimer EpochTimerReseter + epochState EpochState + activeState ActiveState + morphClient *client.Client + } + + // Params of the processor constructor. + Params struct { + Log *zap.Logger + PoolSize int + NetmapContract util.Uint160 + EpochTimer EpochTimerReseter + MorphClient *client.Client + EpochState EpochState + ActiveState ActiveState + } +) + +const ( + newEpochNotification = "NewEpoch" +) + +// New creates network map contract processor instance. +func New(p *Params) (*Processor, error) { + switch { + case p.Log == nil: + return nil, errors.New("ir/netmap: logger is not set") + case p.MorphClient == nil: + return nil, errors.New("ir/netmap: morph client is not set") + case p.EpochTimer == nil: + return nil, errors.New("ir/netmap: epoch itmer is not set") + case p.EpochState == nil: + return nil, errors.New("ir/netmap: global state is not set") + case p.ActiveState == nil: + return nil, errors.New("ir/netmap: global state is not set") + } + + p.Log.Debug("netmap worker pool", zap.Int("size", p.PoolSize)) + + pool, err := ants.NewPool(p.PoolSize, ants.WithNonblocking(true)) + if err != nil { + return nil, errors.Wrap(err, "ir/netmap: can't create worker pool") + } + + return &Processor{ + log: p.Log, + pool: pool, + netmapContract: p.NetmapContract, + epochTimer: p.EpochTimer, + epochState: p.EpochState, + activeState: p.ActiveState, + morphClient: p.MorphClient, + }, nil +} + +// ListenerParsers for the 'event.Listener' event producer. +func (np *Processor) ListenerParsers() []event.ParserInfo { + var parsers []event.ParserInfo + + // new epoch event + newEpoch := event.ParserInfo{} + newEpoch.SetType(newEpochNotification) + newEpoch.SetScriptHash(np.netmapContract) + newEpoch.SetParser(netmapEvent.ParseNewEpoch) + parsers = append(parsers, newEpoch) + + return parsers +} + +// ListenerHandlers for the 'event.Listener' event producer. +func (np *Processor) ListenerHandlers() []event.HandlerInfo { + var handlers []event.HandlerInfo + + // new epoch handler + newEpoch := event.HandlerInfo{} + newEpoch.SetType(newEpochNotification) + newEpoch.SetScriptHash(np.netmapContract) + newEpoch.SetHandler(np.handleNewEpoch) + handlers = append(handlers, newEpoch) + + return handlers +} + +// TimersHandlers for the 'Timers' event producer. +func (np *Processor) TimersHandlers() []event.HandlerInfo { + var handlers []event.HandlerInfo + + // new epoch handler + newEpoch := event.HandlerInfo{} + newEpoch.SetType(timers.EpochTimer) + newEpoch.SetHandler(np.handleNewEpochTick) + handlers = append(handlers, newEpoch) + + return handlers +} diff --git a/pkg/innerring/state.go b/pkg/innerring/state.go new file mode 100644 index 000000000..378cc2416 --- /dev/null +++ b/pkg/innerring/state.go @@ -0,0 +1,17 @@ +package innerring + +// EpochCounter is a getter for a global epoch counter. +func (s *Server) EpochCounter() uint64 { + return s.epochCounter.Load() +} + +// SetEpochCounter is a setter for contract processors to update global +// epoch counter. +func (s *Server) SetEpochCounter(val uint64) { + s.epochCounter.Store(val) +} + +// IsActive is a getter for a global active flag state. +func (s *Server) IsActive() bool { + return s.activeState.Load() +} diff --git a/pkg/innerring/timers/epoch.go b/pkg/innerring/timers/epoch.go new file mode 100644 index 000000000..a7da6bbbe --- /dev/null +++ b/pkg/innerring/timers/epoch.go @@ -0,0 +1,12 @@ +package timers + +// NewEpochTick is a new epoch local ticker event. +type NewEpochTick struct{} + +// MorphEvent implements Event interface. +func (NewEpochTick) MorphEvent() {} + +// ResetEpochTimer to start it again when event has been processed. +func (t *Timers) ResetEpochTimer() { + t.epoch.timer.Reset(t.epoch.duration) +} diff --git a/pkg/innerring/timers/timers.go b/pkg/innerring/timers/timers.go new file mode 100644 index 000000000..8c0197190 --- /dev/null +++ b/pkg/innerring/timers/timers.go @@ -0,0 +1,86 @@ +package timers + +import ( + "context" + "time" + + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +type ( + localTimer struct { + duration time.Duration + timer *time.Timer + handler event.Handler + } + + // Timers is a component for local inner ring timers to produce local events. + Timers struct { + log *zap.Logger + + epoch localTimer + } + + // Params for timers instance constructor. + Params struct { + Log *zap.Logger + EpochDuration time.Duration + } +) + +const ( + // EpochTimer is a type for HandlerInfo structure. + EpochTimer = "EpochTimer" +) + +// New creates instance of timers component. +func New(p *Params) *Timers { + return &Timers{ + log: p.Log, + epoch: localTimer{duration: p.EpochDuration}, + } +} + +// Start runs all available local timers. +func (t *Timers) Start(ctx context.Context) { + t.epoch.timer = time.NewTimer(t.epoch.duration) + go t.serve(ctx) +} + +func (t *Timers) serve(ctx context.Context) { + for { + select { + case <-ctx.Done(): + t.log.Info("timers are getting stopped") + t.epoch.timer.Stop() + + return + case <-t.epoch.timer.C: + // reset timer so it can tick once again + t.epoch.timer.Reset(t.epoch.duration) + + // call handler if it is set + if t.epoch.handler != nil { + t.epoch.handler(NewEpochTick{}) + } + } + } +} + +// RegisterHandler of local timers events. +func (t *Timers) RegisterHandler(h event.HandlerInfo) error { + if h.Handler() == nil { + return errors.New("ir/timers: can't register nil handler") + } + + switch h.GetType() { + case EpochTimer: + t.epoch.handler = h.Handler() + default: + return errors.New("ir/timers: unknown handler type") + } + + return nil +} diff --git a/lib/buckets/boltdb/boltdb.go b/pkg/local_object_storage/bucket/boltdb/boltdb.go similarity index 58% rename from lib/buckets/boltdb/boltdb.go rename to pkg/local_object_storage/bucket/boltdb/boltdb.go index 4310151b1..72c3e87dc 100644 --- a/lib/buckets/boltdb/boltdb.go +++ b/pkg/local_object_storage/bucket/boltdb/boltdb.go @@ -6,15 +6,14 @@ import ( "os" "path" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/core" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" "github.com/pkg/errors" "github.com/spf13/viper" "go.etcd.io/bbolt" ) type ( - bucket struct { + boltBucket struct { db *bbolt.DB name []byte } @@ -28,13 +27,11 @@ type ( } ) -const ( - defaultFilePermission = 0777 +const defaultFilePermission = 0777 - errEmptyPath = internal.Error("database empty path") -) +var errEmptyPath = errors.New("database empty path") -var _ core.Bucket = (*bucket)(nil) +const name = "boltbucket" func makeCopy(val []byte) []byte { tmp := make([]byte, len(val)) @@ -44,8 +41,7 @@ func makeCopy(val []byte) []byte { } // NewOptions prepares options for badger instance. -func NewOptions(name core.BucketType, v *viper.Viper) (opts Options, err error) { - key := string(name) +func NewOptions(v *viper.Viper) (opts Options, err error) { opts = Options{ Options: bbolt.Options{ // set defaults: @@ -53,30 +49,30 @@ func NewOptions(name core.BucketType, v *viper.Viper) (opts Options, err error) FreelistType: bbolt.DefaultOptions.FreelistType, // set config options: - NoSync: v.GetBool(key + ".no_sync"), - ReadOnly: v.GetBool(key + ".read_only"), - NoGrowSync: v.GetBool(key + ".no_grow_sync"), - NoFreelistSync: v.GetBool(key + ".no_freelist_sync"), + NoSync: v.GetBool(name + ".no_sync"), + ReadOnly: v.GetBool(name + ".read_only"), + NoGrowSync: v.GetBool(name + ".no_grow_sync"), + NoFreelistSync: v.GetBool(name + ".no_freelist_sync"), - PageSize: v.GetInt(key + ".page_size"), - MmapFlags: v.GetInt(key + ".mmap_flags"), - InitialMmapSize: v.GetInt(key + ".initial_mmap_size"), + PageSize: v.GetInt(name + ".page_size"), + MmapFlags: v.GetInt(name + ".mmap_flags"), + InitialMmapSize: v.GetInt(name + ".initial_mmap_size"), }, Name: []byte(name), Perm: defaultFilePermission, - Path: v.GetString(key + ".path"), + Path: v.GetString(name + ".path"), } if opts.Path == "" { return opts, errEmptyPath } - if tmp := v.GetDuration(key + ".lock_timeout"); tmp > 0 { + if tmp := v.GetDuration(name + ".lock_timeout"); tmp > 0 { opts.Timeout = tmp } - if perm := v.GetUint32(key + ".perm"); perm != 0 { + if perm := v.GetUint32(name + ".perm"); perm != 0 { opts.Perm = os.FileMode(perm) } @@ -89,7 +85,7 @@ func NewOptions(name core.BucketType, v *viper.Viper) (opts Options, err error) } // NewBucket creates badger-bucket instance. -func NewBucket(opts *Options) (core.Bucket, error) { +func NewBucket(opts *Options) (bucket.Bucket, error) { log.SetOutput(ioutil.Discard) // disable default logger db, err := bbolt.Open(opts.Path, opts.Perm, &opts.Options) @@ -105,5 +101,5 @@ func NewBucket(opts *Options) (core.Bucket, error) { return nil, err } - return &bucket{db: db, name: opts.Name}, nil + return &boltBucket{db: db, name: opts.Name}, nil } diff --git a/lib/buckets/boltdb/methods.go b/pkg/local_object_storage/bucket/boltdb/methods.go similarity index 67% rename from lib/buckets/boltdb/methods.go rename to pkg/local_object_storage/bucket/boltdb/methods.go index b302a7dbd..b226bf7bc 100644 --- a/lib/buckets/boltdb/methods.go +++ b/pkg/local_object_storage/bucket/boltdb/methods.go @@ -4,18 +4,18 @@ import ( "os" "github.com/mr-tron/base58" - "github.com/nspcc-dev/neofs-node/lib/core" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" "github.com/pkg/errors" "go.etcd.io/bbolt" ) // Get value by key or return error. -func (b *bucket) Get(key []byte) (data []byte, err error) { +func (b *boltBucket) Get(key []byte) (data []byte, err error) { err = b.db.View(func(txn *bbolt.Tx) error { txn.Bucket(b.name).Cursor().Seek(key) val := txn.Bucket(b.name).Get(key) if val == nil { - return errors.Wrapf(core.ErrNotFound, "key=%s", base58.Encode(key)) + return errors.Wrapf(bucket.ErrNotFound, "key=%s", base58.Encode(key)) } data = makeCopy(val) @@ -26,7 +26,7 @@ func (b *bucket) Get(key []byte) (data []byte, err error) { } // Set value for key. -func (b *bucket) Set(key, value []byte) error { +func (b *boltBucket) Set(key, value []byte) error { return b.db.Update(func(txn *bbolt.Tx) error { k, v := makeCopy(key), makeCopy(value) return txn.Bucket(b.name).Put(k, v) @@ -34,20 +34,20 @@ func (b *bucket) Set(key, value []byte) error { } // Del removes item from bucket by key. -func (b *bucket) Del(key []byte) error { +func (b *boltBucket) Del(key []byte) error { return b.db.Update(func(txn *bbolt.Tx) error { return txn.Bucket(b.name).Delete(key) }) } // Has checks key exists. -func (b *bucket) Has(key []byte) bool { +func (b *boltBucket) Has(key []byte) bool { _, err := b.Get(key) - return !errors.Is(errors.Cause(err), core.ErrNotFound) + return !errors.Is(errors.Cause(err), bucket.ErrNotFound) } // Size returns size of database. -func (b *bucket) Size() int64 { +func (b *boltBucket) Size() int64 { info, err := os.Stat(b.db.Path()) if err != nil { return 0 @@ -57,7 +57,7 @@ func (b *bucket) Size() int64 { } // List all items in bucket. -func (b *bucket) List() ([][]byte, error) { +func (b *boltBucket) List() ([][]byte, error) { var items [][]byte if err := b.db.View(func(txn *bbolt.Tx) error { @@ -73,15 +73,15 @@ func (b *bucket) List() ([][]byte, error) { } // Filter elements by filter closure. -func (b *bucket) Iterate(handler core.FilterHandler) error { +func (b *boltBucket) Iterate(handler bucket.FilterHandler) error { if handler == nil { - return core.ErrNilFilterHandler + return bucket.ErrNilFilterHandler } return b.db.View(func(txn *bbolt.Tx) error { return txn.Bucket(b.name).ForEach(func(k, v []byte) error { if !handler(makeCopy(k), makeCopy(v)) { - return core.ErrIteratingAborted + return bucket.ErrIteratingAborted } return nil }) @@ -89,6 +89,6 @@ func (b *bucket) Iterate(handler core.FilterHandler) error { } // Close bucket database. -func (b *bucket) Close() error { +func (b *boltBucket) Close() error { return b.db.Close() } diff --git a/pkg/local_object_storage/bucket/bucket.go b/pkg/local_object_storage/bucket/bucket.go new file mode 100644 index 000000000..612e89919 --- /dev/null +++ b/pkg/local_object_storage/bucket/bucket.go @@ -0,0 +1,41 @@ +package bucket + +import ( + "errors" +) + +// FilterHandler where you receive key/val in your closure. +type FilterHandler func(key, val []byte) bool + +// BucketItem used in filter. +type BucketItem struct { + Key []byte + Val []byte +} + +// Bucket is sub-store interface. +type Bucket interface { + Get(key []byte) ([]byte, error) + Set(key, value []byte) error + Del(key []byte) error + Has(key []byte) bool + Size() int64 + List() ([][]byte, error) + Iterate(FilterHandler) error + // Steam can be implemented by badger.Stream, but not for now + // Stream(ctx context.Context, key []byte, cb func(io.ReadWriter) error) error + Close() error +} + +var ( + // ErrNilFilterHandler when FilterHandler is empty + ErrNilFilterHandler = errors.New("handler can't be nil") + + // ErrNotFound is returned by key-value storage methods + // that could not find element by key. + ErrNotFound = errors.New("key not found") +) + +// ErrIteratingAborted is returned by storage iterator +// after iteration has been interrupted. +var ErrIteratingAborted = errors.New("iteration aborted") diff --git a/lib/buckets/fsbucket/bucket.go b/pkg/local_object_storage/bucket/fsbucket/bucket.go similarity index 60% rename from lib/buckets/fsbucket/bucket.go rename to pkg/local_object_storage/bucket/fsbucket/bucket.go index 029d509c9..24c9e3143 100644 --- a/lib/buckets/fsbucket/bucket.go +++ b/pkg/local_object_storage/bucket/fsbucket/bucket.go @@ -4,15 +4,14 @@ import ( "os" "github.com/mr-tron/base58" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/core" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" "github.com/pkg/errors" "github.com/spf13/viper" "go.uber.org/atomic" ) type ( - bucket struct { + Bucket struct { dir string perm os.FileMode } @@ -27,6 +26,8 @@ type ( } ) +const name = "fsbucket" + const ( defaultDirectory = "fsbucket" defaultPermissions = 0755 @@ -34,9 +35,7 @@ const ( defaultPrefixLen = 2 ) -const errShortKey = internal.Error("key is too short for tree fs bucket") - -var _ core.Bucket = (*bucket)(nil) +var errShortKey = errors.New("key is too short for tree fs bucket") func stringifyKey(key []byte) string { return base58.Encode(key) @@ -51,10 +50,9 @@ func decodeKey(key string) []byte { return k } -// NewBucket creates new in-memory bucket instance. -func NewBucket(name core.BucketType, v *viper.Viper) (core.Bucket, error) { +// NewBucket creates new file system bucket instance. +func NewBucket(v *viper.Viper) (bucket.Bucket, error) { var ( - key = "storage." + string(name) dir string perm os.FileMode @@ -62,27 +60,27 @@ func NewBucket(name core.BucketType, v *viper.Viper) (core.Bucket, error) { depth int ) - if dir = v.GetString(key + ".directory"); dir == "" { + if dir = v.GetString(name + ".directory"); dir == "" { dir = defaultDirectory } - if perm = os.FileMode(v.GetInt(key + ".permissions")); perm == 0 { + if perm = os.FileMode(v.GetInt(name + ".permissions")); perm == 0 { perm = defaultPermissions } - if depth = v.GetInt(key + ".depth"); depth <= 0 { + if depth = v.GetInt(name + ".depth"); depth <= 0 { depth = defaultDepth } - if prefixLen = v.GetInt(key + ".prefix_len"); prefixLen <= 0 { + if prefixLen = v.GetInt(name + ".prefix_len"); prefixLen <= 0 { prefixLen = defaultPrefixLen } if err := os.MkdirAll(dir, perm); err != nil { - return nil, errors.Wrapf(err, "could not create bucket %s", string(name)) + return nil, errors.Wrapf(err, "could not create bucket %s", name) } - if v.GetBool(key + ".tree_enabled") { + if v.GetBool(name + ".tree_enabled") { b := &treeBucket{ dir: dir, perm: perm, @@ -94,7 +92,7 @@ func NewBucket(name core.BucketType, v *viper.Viper) (core.Bucket, error) { return b, nil } - return &bucket{ + return &Bucket{ dir: dir, perm: perm, }, nil diff --git a/lib/buckets/fsbucket/methods.go b/pkg/local_object_storage/bucket/fsbucket/methods.go similarity index 74% rename from lib/buckets/fsbucket/methods.go rename to pkg/local_object_storage/bucket/fsbucket/methods.go index 9aeaf45f2..1dd2aea92 100644 --- a/lib/buckets/fsbucket/methods.go +++ b/pkg/local_object_storage/bucket/fsbucket/methods.go @@ -6,38 +6,38 @@ import ( "path" "path/filepath" - "github.com/nspcc-dev/neofs-node/lib/core" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" ) // Get value by key. -func (b *bucket) Get(key []byte) ([]byte, error) { +func (b *Bucket) Get(key []byte) ([]byte, error) { p := path.Join(b.dir, stringifyKey(key)) if _, err := os.Stat(p); os.IsNotExist(err) { - return nil, core.ErrNotFound + return nil, bucket.ErrNotFound } return ioutil.ReadFile(p) } // Set value by key. -func (b *bucket) Set(key, value []byte) error { +func (b *Bucket) Set(key, value []byte) error { p := path.Join(b.dir, stringifyKey(key)) return ioutil.WriteFile(p, value, b.perm) } // Del value by key. -func (b *bucket) Del(key []byte) error { +func (b *Bucket) Del(key []byte) error { p := path.Join(b.dir, stringifyKey(key)) if _, err := os.Stat(p); os.IsNotExist(err) { - return core.ErrNotFound + return bucket.ErrNotFound } return os.Remove(p) } // Has checks key exists. -func (b *bucket) Has(key []byte) bool { +func (b *Bucket) Has(key []byte) bool { p := path.Join(b.dir, stringifyKey(key)) _, err := os.Stat(p) @@ -59,7 +59,7 @@ func listing(root string, fn func(path string, info os.FileInfo) error) error { } // Size of bucket. -func (b *bucket) Size() (size int64) { +func (b *Bucket) Size() (size int64) { err := listing(b.dir, func(_ string, info os.FileInfo) error { size += info.Size() return nil @@ -73,7 +73,7 @@ func (b *bucket) Size() (size int64) { } // List all bucket items. -func (b *bucket) List() ([][]byte, error) { +func (b *Bucket) List() ([][]byte, error) { buckets := make([][]byte, 0) err := listing(b.dir, func(p string, info os.FileInfo) error { @@ -85,7 +85,7 @@ func (b *bucket) List() ([][]byte, error) { } // Filter bucket items by closure. -func (b *bucket) Iterate(handler core.FilterHandler) error { +func (b *Bucket) Iterate(handler bucket.FilterHandler) error { return listing(b.dir, func(p string, info os.FileInfo) error { key := decodeKey(info.Name()) val, err := ioutil.ReadFile(p) @@ -94,7 +94,7 @@ func (b *bucket) Iterate(handler core.FilterHandler) error { } if !handler(key, val) { - return core.ErrIteratingAborted + return bucket.ErrIteratingAborted } return nil @@ -102,6 +102,6 @@ func (b *bucket) Iterate(handler core.FilterHandler) error { } // Close bucket (just empty). -func (b *bucket) Close() error { +func (b *Bucket) Close() error { return os.RemoveAll(b.dir) } diff --git a/lib/buckets/fsbucket/queue.go b/pkg/local_object_storage/bucket/fsbucket/queue.go similarity index 100% rename from lib/buckets/fsbucket/queue.go rename to pkg/local_object_storage/bucket/fsbucket/queue.go diff --git a/lib/buckets/fsbucket/treemethods.go b/pkg/local_object_storage/bucket/fsbucket/treemethods.go similarity index 95% rename from lib/buckets/fsbucket/treemethods.go rename to pkg/local_object_storage/bucket/fsbucket/treemethods.go index 1a1927a82..b427e0c72 100644 --- a/lib/buckets/fsbucket/treemethods.go +++ b/pkg/local_object_storage/bucket/fsbucket/treemethods.go @@ -7,7 +7,7 @@ import ( "path" "strings" - "github.com/nspcc-dev/neofs-node/lib/core" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" ) const queueCap = 1000 @@ -55,7 +55,7 @@ func (b *treeBucket) Get(key []byte) ([]byte, error) { p := path.Join(b.dir, path.Join(dirPaths...), filename) if _, err := os.Stat(p); os.IsNotExist(err) { - return nil, core.ErrNotFound + return nil, bucket.ErrNotFound } return ioutil.ReadFile(p) @@ -99,7 +99,7 @@ func (b *treeBucket) Del(key []byte) error { ) if fi, err = os.Stat(p); os.IsNotExist(err) { - return core.ErrNotFound + return bucket.ErrNotFound } else if err = os.Remove(p); err == nil { b.sz.Sub(fi.Size()) } @@ -218,9 +218,9 @@ func (b *treeBucket) List() ([][]byte, error) { } // Filter bucket items by closure. -func (b *treeBucket) Iterate(handler core.FilterHandler) error { +func (b *treeBucket) Iterate(handler bucket.FilterHandler) error { return b.listing(b.dir, func(p string, info os.FileInfo) error { - val, err := ioutil.ReadFile(path.Join(b.dir, p)) + val, err := ioutil.ReadFile(p) if err != nil { return err } @@ -231,7 +231,7 @@ func (b *treeBucket) Iterate(handler core.FilterHandler) error { } if !handler(key, val) { - return core.ErrIteratingAborted + return bucket.ErrIteratingAborted } return nil diff --git a/lib/buckets/fsbucket/treemethods_test.go b/pkg/local_object_storage/bucket/fsbucket/treemethods_test.go similarity index 97% rename from lib/buckets/fsbucket/treemethods_test.go rename to pkg/local_object_storage/bucket/fsbucket/treemethods_test.go index f0e88e554..402fcf00d 100644 --- a/lib/buckets/fsbucket/treemethods_test.go +++ b/pkg/local_object_storage/bucket/fsbucket/treemethods_test.go @@ -11,10 +11,9 @@ import ( "strings" "testing" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" "github.com/stretchr/testify/require" "go.uber.org/atomic" - - "github.com/nspcc-dev/neofs-node/lib/core" ) func prepareTree(badFiles bool) (string, error) { @@ -272,7 +271,7 @@ func BenchmarkFilewalkBucket_List(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - buckets := make([]core.BucketItem, 0) + buckets := make([]bucket.BucketItem, 0) filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { @@ -289,7 +288,7 @@ func BenchmarkFilewalkBucket_List(b *testing.B) { return err } - buckets = append(buckets, core.BucketItem{ + buckets = append(buckets, bucket.BucketItem{ Key: key, Val: val, }) diff --git a/lib/test/bucket.go b/pkg/local_object_storage/bucket/test/bucket.go similarity index 82% rename from lib/test/bucket.go rename to pkg/local_object_storage/bucket/test/bucket.go index 024a2ab46..78cdf240d 100644 --- a/lib/test/bucket.go +++ b/pkg/local_object_storage/bucket/test/bucket.go @@ -1,12 +1,12 @@ package test import ( + "errors" "sync" "github.com/mr-tron/base58" "github.com/nspcc-dev/neofs-api-go/object" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/core" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" ) type ( @@ -16,13 +16,13 @@ type ( } ) -const ( - errOverflow = internal.Error("overflow") - errNotFound = internal.Error("not found") +var ( + errOverflow = errors.New("overflow") + errNotFound = errors.New("not found") ) -// Bucket constructs test core.Bucket implementation. -func Bucket() core.Bucket { +// Bucket constructs test Bucket implementation. +func Bucket() bucket.Bucket { return &testBucket{ items: make(map[string][]byte), } @@ -34,7 +34,7 @@ func (t *testBucket) Get(key []byte) ([]byte, error) { val, ok := t.items[base58.Encode(key)] if !ok { - return nil, core.ErrNotFound + return nil, bucket.ErrNotFound } return val, nil @@ -96,7 +96,7 @@ func (t *testBucket) List() ([][]byte, error) { return res, nil } -func (t *testBucket) Iterate(f core.FilterHandler) error { +func (t *testBucket) Iterate(f bucket.FilterHandler) error { t.RLock() defer t.RUnlock() @@ -107,7 +107,7 @@ func (t *testBucket) Iterate(f core.FilterHandler) error { } if !f(key, v) { - return core.ErrIteratingAborted + return bucket.ErrIteratingAborted } } diff --git a/lib/localstore/alias.go b/pkg/local_object_storage/localstore/alias.go similarity index 100% rename from lib/localstore/alias.go rename to pkg/local_object_storage/localstore/alias.go diff --git a/lib/localstore/del.go b/pkg/local_object_storage/localstore/del.go similarity index 89% rename from lib/localstore/del.go rename to pkg/local_object_storage/localstore/del.go index f09f40868..1a1859f1f 100644 --- a/lib/localstore/del.go +++ b/pkg/local_object_storage/localstore/del.go @@ -2,7 +2,7 @@ package localstore import ( "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/nspcc-dev/neofs-node/lib/metrics" + metrics2 "github.com/nspcc-dev/neofs-node/pkg/services/metrics" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -31,7 +31,7 @@ func (l *localstore) Del(key refs.Address) error { l.col.UpdateContainer( key.CID, obj.SystemHeader.PayloadLength, - metrics.RemSpace) + metrics2.RemSpace) } return nil diff --git a/lib/localstore/filter.go b/pkg/local_object_storage/localstore/filter.go similarity index 98% rename from lib/localstore/filter.go rename to pkg/local_object_storage/localstore/filter.go index a568e7d9b..f27a37674 100644 --- a/lib/localstore/filter.go +++ b/pkg/local_object_storage/localstore/filter.go @@ -6,7 +6,6 @@ import ( "sort" "sync" - "github.com/nspcc-dev/neofs-node/internal" "github.com/pkg/errors" ) @@ -228,7 +227,7 @@ func (p *filterPipeline) PutSubFilter(params SubFilterParams) error { defer p.Unlock() if params.FilterPipeline == nil { - return internal.Error("could not put sub filter: empty filter pipeline") + return errors.New("could not put sub filter: empty filter pipeline") } name := params.FilterPipeline.GetName() diff --git a/lib/localstore/filter_funcs.go b/pkg/local_object_storage/localstore/filter_funcs.go similarity index 100% rename from lib/localstore/filter_funcs.go rename to pkg/local_object_storage/localstore/filter_funcs.go diff --git a/lib/localstore/filter_test.go b/pkg/local_object_storage/localstore/filter_test.go similarity index 89% rename from lib/localstore/filter_test.go rename to pkg/local_object_storage/localstore/filter_test.go index c07b9fe0c..2da66b7f5 100644 --- a/lib/localstore/filter_test.go +++ b/pkg/local_object_storage/localstore/filter_test.go @@ -2,9 +2,9 @@ package localstore import ( "context" + "errors" "testing" - "github.com/nspcc-dev/neofs-node/internal" "github.com/stretchr/testify/require" ) @@ -17,7 +17,7 @@ func TestFilterResult(t *testing.T) { var ( r *FilterResult c = CodePass - e = internal.Error("test error") + e = errors.New("test error") ) r = ResultPass() diff --git a/lib/localstore/get.go b/pkg/local_object_storage/localstore/get.go similarity index 100% rename from lib/localstore/get.go rename to pkg/local_object_storage/localstore/get.go diff --git a/lib/localstore/has.go b/pkg/local_object_storage/localstore/has.go similarity index 100% rename from lib/localstore/has.go rename to pkg/local_object_storage/localstore/has.go diff --git a/lib/localstore/interface.go b/pkg/local_object_storage/localstore/interface.go similarity index 84% rename from lib/localstore/interface.go rename to pkg/local_object_storage/localstore/interface.go index b1b14b4d0..236c8952c 100644 --- a/lib/localstore/interface.go +++ b/pkg/local_object_storage/localstore/interface.go @@ -4,8 +4,8 @@ import ( "context" "github.com/nspcc-dev/neofs-api-go/object" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/metrics" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" + metrics2 "github.com/nspcc-dev/neofs-node/pkg/services/metrics" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -41,18 +41,18 @@ type ( // Params groups the parameters of // local object storage constructor. Params struct { - BlobBucket core.Bucket - MetaBucket core.Bucket + BlobBucket bucket.Bucket + MetaBucket bucket.Bucket Logger *zap.Logger - Collector metrics.Collector + Collector metrics2.Collector } localstore struct { - metaBucket core.Bucket - blobBucket core.Bucket + metaBucket bucket.Bucket + blobBucket bucket.Bucket log *zap.Logger - col metrics.Collector + col metrics2.Collector } ) @@ -72,9 +72,9 @@ var errNilCollector = errors.New("metrics collector is nil") func New(p Params) (Localstore, error) { switch { case p.MetaBucket == nil: - return nil, errors.Errorf("%s bucket is nil", core.MetaStore) + return nil, errors.New("meta bucket is nil") case p.BlobBucket == nil: - return nil, errors.Errorf("%s bucket is nil", core.BlobStore) + return nil, errors.New("blob bucket is nil") case p.Logger == nil: return nil, errNilLogger case p.Collector == nil: diff --git a/lib/localstore/list.go b/pkg/local_object_storage/localstore/list.go similarity index 100% rename from lib/localstore/list.go rename to pkg/local_object_storage/localstore/list.go diff --git a/lib/localstore/localstore.pb.go b/pkg/local_object_storage/localstore/localstore.pb.go similarity index 84% rename from lib/localstore/localstore.pb.go rename to pkg/local_object_storage/localstore/localstore.pb.go index e6c13b373..8700f28ea 100644 Binary files a/lib/localstore/localstore.pb.go and b/pkg/local_object_storage/localstore/localstore.pb.go differ diff --git a/lib/localstore/localstore.proto b/pkg/local_object_storage/localstore/localstore.proto similarity index 78% rename from lib/localstore/localstore.proto rename to pkg/local_object_storage/localstore/localstore.proto index db5ec83e9..b1fd60674 100644 --- a/lib/localstore/localstore.proto +++ b/pkg/local_object_storage/localstore/localstore.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -option go_package = "github.com/nspcc-dev/neofs-node/lib/localstore"; +option go_package = "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore"; package localstore; diff --git a/lib/localstore/localstore_test.go b/pkg/local_object_storage/localstore/localstore_test.go similarity index 80% rename from lib/localstore/localstore_test.go rename to pkg/local_object_storage/localstore/localstore_test.go index 06925e6bd..761e2739c 100644 --- a/lib/localstore/localstore_test.go +++ b/pkg/local_object_storage/localstore/localstore_test.go @@ -10,38 +10,33 @@ import ( "github.com/nspcc-dev/neofs-api-go/hash" "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/nspcc-dev/neofs-node/lib/meta" - "github.com/nspcc-dev/neofs-node/lib/metrics" - "github.com/nspcc-dev/neofs-node/lib/test" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket/test" + meta2 "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/meta" + metrics2 "github.com/nspcc-dev/neofs-node/pkg/services/metrics" "github.com/stretchr/testify/require" "go.uber.org/zap" ) type ( - testBucket struct { - sync.RWMutex - items map[string][]byte - } - fakeCollector struct { sync.Mutex items map[refs.CID]uint64 } ) -func (f *fakeCollector) Start(_ context.Context) { panic("implement me") } -func (f *fakeCollector) UpdateSpaceUsage() { panic("implement me") } -func (f *fakeCollector) SetIterator(_ meta.Iterator) { panic("implement me") } -func (f *fakeCollector) SetCounter(counter metrics.ObjectCounter) { panic("implement me") } +func (f *fakeCollector) Start(_ context.Context) { panic("implement me") } +func (f *fakeCollector) UpdateSpaceUsage() { panic("implement me") } +func (f *fakeCollector) SetIterator(_ meta2.Iterator) { panic("implement me") } +func (f *fakeCollector) SetCounter(counter metrics2.ObjectCounter) { panic("implement me") } -func (f *fakeCollector) UpdateContainer(cid refs.CID, size uint64, op metrics.SpaceOp) { +func (f *fakeCollector) UpdateContainer(cid refs.CID, size uint64, op metrics2.SpaceOp) { f.Lock() defer f.Unlock() switch op { - case metrics.AddSpace: + case metrics2.AddSpace: f.items[cid] += size - case metrics.RemSpace: + case metrics2.RemSpace: if val, ok := f.items[cid]; !ok || val < size { return } @@ -52,90 +47,12 @@ func (f *fakeCollector) UpdateContainer(cid refs.CID, size uint64, op metrics.Sp } } -func newCollector() metrics.Collector { +func newCollector() metrics2.Collector { return &fakeCollector{ items: make(map[refs.CID]uint64), } } -func newTestBucket() *testBucket { - return &testBucket{ - items: make(map[string][]byte), - } -} - -// -// func (t *testBucket) Get(key []byte) ([]byte, error) { -// t.Lock() -// defer t.Unlock() -// -// val, ok := t.items[base58.Encode(key)] -// if !ok { -// return nil, errors.New("item not found") -// } -// -// return val, nil -// } -// -// func (t *testBucket) Set(key, value []byte) error { -// t.Lock() -// defer t.Unlock() -// -// t.items[base58.Encode(key)] = value -// -// return nil -// } -// -// func (t *testBucket) Del(key []byte) error { -// t.RLock() -// defer t.RUnlock() -// -// delete(t.items, base58.Encode(key)) -// -// return nil -// } -// -// func (t *testBucket) Has(key []byte) bool { -// panic("implement me") -// } -// -// func (t *testBucket) Size() int64 { -// panic("implement me") -// } -// -// func (t *testBucket) List() ([]core.BucketItem, error) { -// t.Lock() -// defer t.Unlock() -// -// res := make([]core.BucketItem, 0) -// -// for k, v := range t.items { -// sk, err := base58.Decode(k) -// if err != nil { -// return nil, err -// } -// -// res = append(res, core.BucketItem{ -// Key: sk, -// Val: v, -// }) -// } -// -// return res, nil -// } -// -// func (t *testBucket) Filter(core.FilterHandler) ([]core.BucketItem, error) { -// panic("implement me") -// } -// -// func (t *testBucket) Close() error { -// panic("implement me") -// } -// -// func (t *testBucket) PRead(key []byte, rng object.Range) ([]byte, error) { -// panic("implement me") -// } - func testObject(t *testing.T) *Object { var ( uid refs.UUID diff --git a/lib/localstore/meta.go b/pkg/local_object_storage/localstore/meta.go similarity index 100% rename from lib/localstore/meta.go rename to pkg/local_object_storage/localstore/meta.go diff --git a/lib/localstore/put.go b/pkg/local_object_storage/localstore/put.go similarity index 91% rename from lib/localstore/put.go rename to pkg/local_object_storage/localstore/put.go index 6f0421429..bc18475d8 100644 --- a/lib/localstore/put.go +++ b/pkg/local_object_storage/localstore/put.go @@ -4,7 +4,7 @@ import ( "context" "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/nspcc-dev/neofs-node/lib/metrics" + metrics2 "github.com/nspcc-dev/neofs-node/pkg/services/metrics" "github.com/pkg/errors" ) @@ -41,7 +41,7 @@ func (l *localstore) Put(ctx context.Context, obj *Object) error { l.col.UpdateContainer( obj.SystemHeader.CID, obj.SystemHeader.PayloadLength, - metrics.AddSpace) + metrics2.AddSpace) return nil } diff --git a/lib/localstore/range.go b/pkg/local_object_storage/localstore/range.go similarity index 100% rename from lib/localstore/range.go rename to pkg/local_object_storage/localstore/range.go diff --git a/lib/meta/iterator.go b/pkg/local_object_storage/meta/iterator.go similarity index 100% rename from lib/meta/iterator.go rename to pkg/local_object_storage/meta/iterator.go diff --git a/pkg/morph/client/balance/balanceOf.go b/pkg/morph/client/balance/balanceOf.go new file mode 100644 index 000000000..2a6e16c2b --- /dev/null +++ b/pkg/morph/client/balance/balanceOf.go @@ -0,0 +1,52 @@ +package balance + +import ( + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/pkg/errors" +) + +// GetBalanceOfArgs groups the arguments +// of "balance of" test invoke call. +type GetBalanceOfArgs struct { + wallet []byte // wallet script hash +} + +// GetBalanceOfValues groups the stack parameters +// returned by "balance of" test invoke. +type GetBalanceOfValues struct { + amount int64 // wallet funds amount +} + +// SetWallet sets the wallet script hash +// in a binary format. +func (g *GetBalanceOfArgs) SetWallet(v []byte) { + g.wallet = v +} + +// Amount returns the amount of funds. +func (g *GetBalanceOfValues) Amount() int64 { + return g.amount +} + +// BalanceOf performs the test invoke of "balance of" +// method of NeoFS Balance contract. +func (c *Client) BalanceOf(args GetBalanceOfArgs) (*GetBalanceOfValues, error) { + prms, err := c.client.TestInvoke( + c.balanceOfMethod, + args.wallet, + ) + if err != nil { + return nil, errors.Wrapf(err, "could not perform test invocation (%s)", c.balanceOfMethod) + } else if ln := len(prms); ln != 1 { + return nil, errors.Errorf("unexpected stack item count (%s): %d", c.balanceOfMethod, ln) + } + + amount, err := client.IntFromStackParameter(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get integer stack item from stack item (%s)", c.balanceOfMethod) + } + + return &GetBalanceOfValues{ + amount: amount, + }, nil +} diff --git a/pkg/morph/client/balance/client.go b/pkg/morph/client/balance/client.go new file mode 100644 index 000000000..d7c1a38b0 --- /dev/null +++ b/pkg/morph/client/balance/client.go @@ -0,0 +1,102 @@ +package balance + +import ( + "errors" + + "github.com/nspcc-dev/neofs-node/pkg/morph/client" +) + +// Client is a wrapper over StaticClient +// which makes calls with the names and arguments +// of the NeoFS Balance contract. +// +// Working client must be created via constructor New. +// Using the Client that has been created with new(Client) +// expression (or just declaring a Client variable) is unsafe +// and can lead to panic. +type Client struct { + client *client.StaticClient // static Balance contract client + + *cfg // contract method names +} + +// ErrNilClient is returned by functions that expect +// a non-nil Client pointer, but received nil. +var ErrNilClient = errors.New("balance contract client is nil") + +// Option is a client configuration change function. +type Option func(*cfg) + +type cfg struct { + balanceOfMethod, // balanceOf method name for invocation + decimalsMethod string // decimals method name for invocation +} + +const ( + defaultBalanceOfMethod = "balanceOf" // default "balance of" method name + defaultDecimalsMethod = "decimals" // default decimals method name +) + +func defaultConfig() *cfg { + return &cfg{ + balanceOfMethod: defaultBalanceOfMethod, + decimalsMethod: defaultDecimalsMethod, + } +} + +// New creates, initializes and returns the Client instance. +// +// If StaticClient is nil, client.ErrNilStaticClient is returned. +// +// Other values are set according to provided options, or by default: +// * "balance of" method name: balanceOf; +// * decimals method name: decimals. +// +// If desired option satisfies the default value, it can be omitted. +// If multiple options of the same config value are supplied, +// the option with the highest index in the arguments will be used. +func New(c *client.StaticClient, opts ...Option) (*Client, error) { + if c == nil { + return nil, client.ErrNilStaticClient + } + + res := &Client{ + client: c, + cfg: defaultConfig(), // build default configuration + } + + // apply options + for _, opt := range opts { + opt(res.cfg) + } + + return res, nil +} + +// WithBalanceOfMethod returns a client constructor option that +// specifies the "balance of" method name. +// +// Ignores empty value. +// +// If option not provided, "balanceOf" is used. +func WithBalanceOfMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.balanceOfMethod = n + } + } +} + +// WithDecimalsMethod returns a client constructor option that +// specifies the method name of decimals receiving operation. +// +// Ignores empty value. +// +// If option not provided, "decimals" is used. +func WithDecimalsMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.decimalsMethod = n + } + } +} diff --git a/pkg/morph/client/balance/decimals.go b/pkg/morph/client/balance/decimals.go new file mode 100644 index 000000000..12169d85a --- /dev/null +++ b/pkg/morph/client/balance/decimals.go @@ -0,0 +1,44 @@ +package balance + +import ( + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/pkg/errors" +) + +// DecimalsArgs groups the arguments +// of decimals test invoke call. +type DecimalsArgs struct { +} + +// DecimalsValues groups the stack parameters +// returned by decimals test invoke. +type DecimalsValues struct { + decimals int64 // decimals value +} + +// Decimals returns the decimals value. +func (d *DecimalsValues) Decimals() int64 { + return d.decimals +} + +// Decimals performs the test invoke of decimals +// method of NeoFS Balance contract. +func (c *Client) Decimals(args DecimalsArgs) (*DecimalsValues, error) { + prms, err := c.client.TestInvoke( + c.decimalsMethod, + ) + if err != nil { + return nil, errors.Wrapf(err, "could not perform test invocation (%s)", c.decimalsMethod) + } else if ln := len(prms); ln != 1 { + return nil, errors.Errorf("unexpected stack item count (%s): %d", c.decimalsMethod, ln) + } + + decimals, err := client.IntFromStackParameter(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get integer stack item from stack item (%s)", c.decimalsMethod) + } + + return &DecimalsValues{ + decimals: decimals, + }, nil +} diff --git a/pkg/morph/client/balance/wrapper/balanceOf.go b/pkg/morph/client/balance/wrapper/balanceOf.go new file mode 100644 index 000000000..bb1d04910 --- /dev/null +++ b/pkg/morph/client/balance/wrapper/balanceOf.go @@ -0,0 +1,35 @@ +package wrapper + +import ( + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neofs-node/pkg/core/container" + "github.com/nspcc-dev/neofs-node/pkg/morph/client/balance" + "github.com/pkg/errors" +) + +// OwnerID represents the container owner identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.OwnerID. +type OwnerID = container.OwnerID + +// BalanceOf receives the amount of funds in the client's account +// through the Balance contract call, and returns it. +func (w *Wrapper) BalanceOf(ownerID OwnerID) (int64, error) { + // convert Neo wallet address to Uint160 + u160, err := address.StringToUint160(ownerID.String()) + if err != nil { + return 0, errors.Wrap(err, "could not convert wallet address to Uint160") + } + + // prepare invocation arguments + args := balance.GetBalanceOfArgs{} + args.SetWallet(u160.BytesBE()) + + values, err := w.client.BalanceOf(args) + if err != nil { + return 0, errors.Wrap(err, "could not invoke smart contract") + } + + return values.Amount(), nil +} diff --git a/pkg/morph/client/balance/wrapper/decimals.go b/pkg/morph/client/balance/wrapper/decimals.go new file mode 100644 index 000000000..ce13b20ef --- /dev/null +++ b/pkg/morph/client/balance/wrapper/decimals.go @@ -0,0 +1,22 @@ +package wrapper + +import ( + "github.com/nspcc-dev/neofs-node/pkg/morph/client/balance" + "github.com/pkg/errors" +) + +// Decimals decimal precision of currency transactions +// through the Balance contract call, and returns it. +func (w *Wrapper) Decimals() (uint32, error) { + // prepare invocation arguments + args := balance.DecimalsArgs{} + + // invoke smart contract call + values, err := w.client.Decimals(args) + if err != nil { + return 0, errors.Wrap(err, "could not invoke smart contract") + } + + // FIXME: avoid narrowing cast + return uint32(values.Decimals()), nil +} diff --git a/pkg/morph/client/balance/wrapper/wrapper.go b/pkg/morph/client/balance/wrapper/wrapper.go new file mode 100644 index 000000000..317b1deb1 --- /dev/null +++ b/pkg/morph/client/balance/wrapper/wrapper.go @@ -0,0 +1,43 @@ +package wrapper + +import ( + "errors" + + "github.com/nspcc-dev/neofs-node/pkg/morph/client/balance" +) + +// Client represents the Balance contract client. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/morph/client/balance.Client. +type Client = balance.Client + +// Wrapper is a wrapper over balance contract +// client which implements: +// * tool for obtaining the amount of funds in the client's account; +// * tool for obtaining decimal precision of currency transactions. +// +// Working wrapper must be created via constructor New. +// Using the Wrapper that has been created with new(Wrapper) +// expression (or just declaring a Wrapper variable) is unsafe +// and can lead to panic. +type Wrapper struct { + client *Client +} + +// ErrNilWrapper is returned by functions that expect +// a non-nil Wrapper pointer, but received nil. +var ErrNilWrapper = errors.New("balance contract client wrapper is nil") + +// New creates, initializes and returns the Wrapper instance. +// +// If Client is nil, balance.ErrNilClient is returned. +func New(c *Client) (*Wrapper, error) { + if c == nil { + return nil, balance.ErrNilClient + } + + return &Wrapper{ + client: c, + }, nil +} diff --git a/lib/blockchain/goclient/client.go b/pkg/morph/client/client.go similarity index 51% rename from lib/blockchain/goclient/client.go rename to pkg/morph/client/client.go index 977c9b800..308285e1b 100644 --- a/lib/blockchain/goclient/client.go +++ b/pkg/morph/client/client.go @@ -1,67 +1,51 @@ -package goclient +package client import ( - "context" - "crypto/ecdsa" "encoding/hex" - "time" - "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/rpc/client" sc "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" - crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/nspcc-dev/neofs-node/internal" + "github.com/nspcc-dev/neofs-node/pkg/util/logger" "github.com/pkg/errors" "go.uber.org/zap" ) -type ( - // Params is a group of Client's constructor parameters. - Params struct { - Log *zap.Logger - Key *ecdsa.PrivateKey - Endpoint string - Magic netmode.Magic - DialTimeout time.Duration - } +// Client is a neo-go wrapper that provides +// smart-contract invocation interface. +// +// Working client must be created via constructor New. +// Using the Client that has been created with new(Client) +// expression (or just declaring a Client variable) is unsafe +// and can lead to panic. +type Client struct { + logger *logger.Logger // logging component - // Client is a neo-go wrapper that provides smart-contract invocation interface. - Client struct { - log *zap.Logger - cli *client.Client - acc *wallet.Account - } -) + client *client.Client // neo-go client + + acc *wallet.Account // neo account + + gas util.Uint160 // native gas script-hash +} // ErrNilClient is returned by functions that expect -// a non-nil Client, but received nil. -const ErrNilClient = internal.Error("go client is nil") +// a non-nil Client pointer, but received nil. +var ErrNilClient = errors.New("client is nil") // HaltState returned if TestInvoke function processed without panic. const HaltState = "HALT" -// ErrMissingFee is returned by functions that expect -// a positive invocation fee, but received non-positive. -const ErrMissingFee = internal.Error("invocation fee must be positive") +var errEmptyInvocationScript = errors.New("got empty invocation script from neo node") -var ( - errNilParams = errors.New("chain/client: config was not provided to the constructor") - - errNilLogger = errors.New("chain/client: logger was not provided to the constructor") - - errNilKey = errors.New("chain/client: private key was not provided to the constructor") -) +var errScriptDecode = errors.New("could not decode invocation script from neo node") // Invoke invokes contract method by sending transaction into blockchain. // Supported args types: int64, string, util.Uint160, []byte and bool. -// -// If passed fee is non-positive, ErrMissingFee returns. func (c *Client) Invoke(contract util.Uint160, fee util.Fixed8, method string, args ...interface{}) error { - var params []sc.Parameter + params := make([]sc.Parameter, 0, len(args)) + for i := range args { param, err := toStackParameter(args[i]) if err != nil { @@ -78,26 +62,28 @@ func (c *Client) Invoke(contract util.Uint160, fee util.Fixed8, method string, a }, } - resp, err := c.cli.InvokeFunction(contract, method, params, cosigner) + resp, err := c.client.InvokeFunction(contract, method, params, cosigner) if err != nil { return err } if len(resp.Script) == 0 { - return errors.New("chain/client: got empty invocation script from neo node") + return errEmptyInvocationScript } script, err := hex.DecodeString(resp.Script) if err != nil { - return errors.New("chain/client: can't decode invocation script from neo node") + return errScriptDecode } - txHash, err := c.cli.SignAndPushInvocationTx(script, c.acc, 0, fee, cosigner) + sysFee := resp.GasConsumed + int64(fee) // consumed gas + extra fee + + txHash, err := c.client.SignAndPushInvocationTx(script, c.acc, sysFee, 0, cosigner) if err != nil { return err } - c.log.Debug("neo client invoke", + c.logger.Debug("neo client invoke", zap.String("method", method), zap.Stringer("tx_hash", txHash)) @@ -125,7 +111,7 @@ func (c *Client) TestInvoke(contract util.Uint160, method string, args ...interf }, } - val, err := c.cli.InvokeFunction(contract, method, params, cosigner) + val, err := c.client.InvokeFunction(contract, method, params, cosigner) if err != nil { return nil, err } @@ -137,38 +123,18 @@ func (c *Client) TestInvoke(contract util.Uint160, method string, args ...interf return val.Stack, nil } -// New is a Client constructor. -func New(ctx context.Context, p *Params) (*Client, error) { - switch { - case p == nil: - return nil, errNilParams - case p.Log == nil: - return nil, errNilLogger - case p.Key == nil: - return nil, errNilKey - } - - privKeyBytes := crypto.MarshalPrivateKey(p.Key) - - wif, err := keys.WIFEncode(privKeyBytes, keys.WIFVersion, true) +// TransferGas to the receiver from local wallet +func (c *Client) TransferGas(receiver util.Uint160, amount util.Fixed8) error { + txHash, err := c.client.TransferNEP5(c.acc, receiver, c.gas, int64(amount), 0) if err != nil { - return nil, err + return err } - account, err := wallet.NewAccountFromWIF(wif) - if err != nil { - return nil, err - } + c.logger.Debug("native gas transfer invoke", + zap.String("to", receiver.StringLE()), + zap.Stringer("tx_hash", txHash)) - cli, err := client.New(ctx, p.Endpoint, client.Options{ - DialTimeout: p.DialTimeout, - Network: p.Magic, - }) - if err != nil { - return nil, err - } - - return &Client{log: p.Log, cli: cli, acc: account}, nil + return nil } func toStackParameter(value interface{}) (sc.Parameter, error) { diff --git a/lib/blockchain/goclient/client_test.go b/pkg/morph/client/client_test.go similarity index 97% rename from lib/blockchain/goclient/client_test.go rename to pkg/morph/client/client_test.go index 90d2c271a..cffe1201d 100644 --- a/lib/blockchain/goclient/client_test.go +++ b/pkg/morph/client/client_test.go @@ -1,4 +1,4 @@ -package goclient +package client import ( "testing" diff --git a/pkg/morph/client/constructor.go b/pkg/morph/client/constructor.go new file mode 100644 index 000000000..a16917997 --- /dev/null +++ b/pkg/morph/client/constructor.go @@ -0,0 +1,162 @@ +package client + +import ( + "context" + "crypto/ecdsa" + "time" + + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/rpc/client" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/wallet" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-node/pkg/util/logger" + "go.uber.org/zap" +) + +// Option is a client configuration change function. +type Option func(*cfg) + +// groups the configurations with default values. +type cfg struct { + ctx context.Context // neo-go client context + + dialTimeout time.Duration // client dial timeout + + magic netmode.Magic // type of Neo blockchain network + + logger *logger.Logger // logging component + + gas util.Uint160 // native gas script-hash +} + +const defaultDialTimeout = 5 * time.Second + +const defaultMagic = netmode.PrivNet + +func defaultConfig() *cfg { + return &cfg{ + ctx: context.Background(), + dialTimeout: defaultDialTimeout, + magic: defaultMagic, + logger: zap.L(), + gas: util.Uint160{}, + } +} + +// New creates, initializes and returns the Client instance. +// +// If private key is nil, crypto.ErrEmptyPrivateKey is returned. +// +// Other values are set according to provided options, or by default: +// * client context: Background; +// * dial timeout: 5s; +// * blockchain network type: netmode.PrivNet; +// * logger: zap.L(). +// +// If desired option satisfies the default value, it can be omitted. +// If multiple options of the same config value are supplied, +// the option with the highest index in the arguments will be used. +func New(key *ecdsa.PrivateKey, endpoint string, opts ...Option) (*Client, error) { + if key == nil { + return nil, crypto.ErrEmptyPrivateKey + } + + privKeyBytes := crypto.MarshalPrivateKey(key) + + wif, err := keys.WIFEncode(privKeyBytes, keys.WIFVersion, true) + if err != nil { + return nil, err + } + + account, err := wallet.NewAccountFromWIF(wif) + if err != nil { + return nil, err + } + + // build default configuration + cfg := defaultConfig() + + // apply options + for _, opt := range opts { + opt(cfg) + } + + cli, err := client.New(cfg.ctx, endpoint, client.Options{ + DialTimeout: cfg.dialTimeout, + Network: cfg.magic, + }) + if err != nil { + return nil, err + } + + return &Client{ + logger: cfg.logger, + client: cli, + acc: account, + gas: cfg.gas, + }, nil +} + +// WithContext returns a client constructor option that +// specifies the neo-go client context. +// +// Ignores nil value. +// +// If option not provided, context.Background() is used. +func WithContext(ctx context.Context) Option { + return func(c *cfg) { + if ctx != nil { + c.ctx = ctx + } + } +} + +// WithDialTimeout returns a client constructor option +// that specifies neo-go client dial timeout duration. +// +// Ignores non-positive value. +// +// If option not provided, 5s timeout is used. +func WithDialTimeout(dur time.Duration) Option { + return func(c *cfg) { + if dur > 0 { + c.dialTimeout = dur + } + } +} + +// WithMagic returns a client constructor option +// that specifies neo blockchain network type. +// +// If option not provided, netmode.PrivNet is used. +func WithMagic(mag netmode.Magic) Option { + return func(c *cfg) { + c.magic = mag + } +} + +// WithLogger returns a client constructor option +// that specifies the component for writing log messages. +// +// Ignores nil value. +// +// If option not provided, zap.L() is used. +func WithLogger(logger *logger.Logger) Option { + return func(c *cfg) { + if logger != nil { + c.logger = logger + } + } +} + +// WithGasContract returns a client constructor option +// that specifies native gas contract script hash. +// +// If option not provided, empty script hash is used. +func WithGasContract(gas util.Uint160) Option { + return func(c *cfg) { + c.gas = gas + } +} diff --git a/pkg/morph/client/container/client.go b/pkg/morph/client/container/client.go new file mode 100644 index 000000000..e8ad001ef --- /dev/null +++ b/pkg/morph/client/container/client.go @@ -0,0 +1,174 @@ +package container + +import ( + "errors" + + "github.com/nspcc-dev/neofs-node/pkg/morph/client" +) + +// Client is a wrapper over StaticClient +// which makes calls with the names and arguments +// of the NeoFS Container contract. +// +// Working client must be created via constructor New. +// Using the Client that has been created with new(Client) +// expression (or just declaring a Client variable) is unsafe +// and can lead to panic. +type Client struct { + client *client.StaticClient // static Container contract client + + *cfg // contract method names +} + +// ErrNilClient is returned by functions that expect +// a non-nil Client pointer, but received nil. +var ErrNilClient = errors.New("container contract client is nil") + +// Option is a client configuration change function. +type Option func(*cfg) + +type cfg struct { + putMethod, // put container method name for invocation + deleteMethod, // delete container method name for invocation + getMethod, // get container method name for invocation + listMethod, // list container method name for invocation + setEACLMethod, // set eACL method name for invocation + eaclMethod string // get eACL method name for invocation +} + +const ( + defaultPutMethod = "Put" // default put container method name + defaultDeleteMethod = "Delete" // default delete container method name + defaultGetMethod = "Get" // default get container method name + defaultListMethod = "List" // default list containers method name + defaultEACLMethod = "EACL" // default get eACL method name + defaultSetEACLMethod = "SetEACL" // default set eACL method name +) + +func defaultConfig() *cfg { + return &cfg{ + putMethod: defaultPutMethod, + deleteMethod: defaultDeleteMethod, + getMethod: defaultGetMethod, + listMethod: defaultListMethod, + setEACLMethod: defaultSetEACLMethod, + eaclMethod: defaultEACLMethod, + } +} + +// New creates, initializes and returns the Client instance. +// +// If StaticClient is nil, client.ErrNilStaticClient is returned. +// +// Other values are set according to provided options, or by default: +// * put container method name: Put; +// * delete container method name: Delete; +// * get container method name: Get; +// * list containers method name: List; +// * set eACL method name: SetEACL; +// * get eACL method name: EACL. +// +// If desired option satisfies the default value, it can be omitted. +// If multiple options of the same config value are supplied, +// the option with the highest index in the arguments will be used. +func New(c *client.StaticClient, opts ...Option) (*Client, error) { + if c == nil { + return nil, client.ErrNilStaticClient + } + + res := &Client{ + client: c, + cfg: defaultConfig(), // build default configuration + } + + // apply options + for _, opt := range opts { + opt(res.cfg) + } + + return res, nil +} + +// WithPutMethod returns a client constructor option that +// specifies the method name of container storing operation. +// +// Ignores empty value. +// +// If option not provided, "Put" is used. +func WithPutMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.putMethod = n + } + } +} + +// WithDeleteMethod returns a client constructor option that +// specifies the method name of container removal operation. +// +// Ignores empty value. +// +// If option not provided, "Delete" is used. +func WithDeleteMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.deleteMethod = n + } + } +} + +// WithGetMethod returns a client constructor option that +// specifies the method name of container receiving operation. +// +// Ignores empty value. +// +// If option not provided, "Get" is used. +func WithGetMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.getMethod = n + } + } +} + +// WithListMethod returns a client constructor option that +// specifies the method name of container listing operation. +// +// Ignores empty value. +// +// If option not provided, "List" is used. +func WithListMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.listMethod = n + } + } +} + +// WithSetEACLMethod returns a client constructor option that +// specifies the method name of eACL storing operation. +// +// Ignores empty value. +// +// If option not provided, "SetEACL" is used. +func WithSetEACLMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.setEACLMethod = n + } + } +} + +// WithEACLMethod returns a client constructor option that +// specifies the method name of eACL receiving operation. +// +// Ignores empty value. +// +// If option not provided, "EACL" is used. +func WithEACLMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.eaclMethod = n + } + } +} diff --git a/pkg/morph/client/container/delete.go b/pkg/morph/client/container/delete.go new file mode 100644 index 000000000..76bdce3ef --- /dev/null +++ b/pkg/morph/client/container/delete.go @@ -0,0 +1,42 @@ +package container + +import "github.com/pkg/errors" + +// DeleteArgs groups the arguments +// of delete container invocation call. +type DeleteArgs struct { + cid []byte // container identifier + + ownerID []byte // container owner identifier + + sig []byte // container identifier signature +} + +// SetOwnerID sets the container owner identifier +// in a binary format. +func (p *DeleteArgs) SetOwnerID(v []byte) { + p.ownerID = v +} + +// SetCID sets the container identifier +// in a binary format. +func (p *DeleteArgs) SetCID(v []byte) { + p.cid = v +} + +// SetSignature sets the container identifier +// owner's signature. +func (p *DeleteArgs) SetSignature(v []byte) { + p.sig = v +} + +// Delete invokes the call of delete container +// method of NeoFS Container contract. +func (c *Client) Delete(args DeleteArgs) error { + return errors.Wrapf(c.client.Invoke( + c.deleteMethod, + args.cid, + args.ownerID, + args.sig, + ), "could not invoke method (%s)", c.deleteMethod) +} diff --git a/pkg/morph/client/container/eacl.go b/pkg/morph/client/container/eacl.go new file mode 100644 index 000000000..b2ee42b70 --- /dev/null +++ b/pkg/morph/client/container/eacl.go @@ -0,0 +1,53 @@ +package container + +import ( + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/pkg/errors" +) + +// EACLArgs groups the arguments +// of get eACL test invoke call. +type EACLArgs struct { + cid []byte // container identifier +} + +// EACLValues groups the stack parameters +// returned by get eACL test invoke. +type EACLValues struct { + eacl []byte // extended ACL table +} + +// SetCID sets the container identifier +// in a binary format. +func (g *EACLArgs) SetCID(v []byte) { + g.cid = v +} + +// EACL returns the eACL table +// in a binary format. +func (g *EACLValues) EACL() []byte { + return g.eacl +} + +// EACL performs the test invoke of get eACL +// method of NeoFS Container contract. +func (c *Client) EACL(args EACLArgs) (*EACLValues, error) { + prms, err := c.client.TestInvoke( + c.eaclMethod, + args.cid, + ) + if err != nil { + return nil, errors.Wrapf(err, "could not perform test invocation (%s)", c.eaclMethod) + } else if ln := len(prms); ln != 1 { + return nil, errors.Errorf("unexpected stack item count (%s): %d", c.eaclMethod, ln) + } + + eacl, err := client.BytesFromStackParameter(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get byte array from stack item (%s)", c.eaclMethod) + } + + return &EACLValues{ + eacl: eacl, + }, nil +} diff --git a/pkg/morph/client/container/eacl_set.go b/pkg/morph/client/container/eacl_set.go new file mode 100644 index 000000000..fe75f9cea --- /dev/null +++ b/pkg/morph/client/container/eacl_set.go @@ -0,0 +1,42 @@ +package container + +import "github.com/pkg/errors" + +// SetEACLArgs groups the arguments +// of set eACL invocation call. +type SetEACLArgs struct { + cid []byte // container identifier in a binary format + + eacl []byte // extended ACL table + + sig []byte // eACL table signature +} + +// SetCID sets the container identifier +// in a binary format. +func (p *SetEACLArgs) SetCID(v []byte) { + p.cid = v +} + +// SetEACL sets the extended ACL table +// in a binary format. +func (p *SetEACLArgs) SetEACL(v []byte) { + p.eacl = v +} + +// SetSignature sets the eACL table structure +// owner's signature. +func (p *SetEACLArgs) SetSignature(v []byte) { + p.sig = v +} + +// SetEACL invokes the call of set eACL method +// of NeoFS Container contract. +func (c *Client) SetEACL(args SetEACLArgs) error { + return errors.Wrapf(c.client.Invoke( + c.setEACLMethod, + args.cid, + args.eacl, + args.sig, + ), "could not invoke method (%s)", c.setEACLMethod) +} diff --git a/pkg/morph/client/container/get.go b/pkg/morph/client/container/get.go new file mode 100644 index 000000000..fb9e6d866 --- /dev/null +++ b/pkg/morph/client/container/get.go @@ -0,0 +1,53 @@ +package container + +import ( + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/pkg/errors" +) + +// GetArgs groups the arguments +// of get container test invoke call. +type GetArgs struct { + cid []byte // container identifier +} + +// GetValues groups the stack parameters +// returned by get container test invoke. +type GetValues struct { + cnr []byte // container in a binary form +} + +// SetCID sets the container identifier +// in a binary format. +func (g *GetArgs) SetCID(v []byte) { + g.cid = v +} + +// Container returns the container +// in a binary format. +func (g *GetValues) Container() []byte { + return g.cnr +} + +// Get performs the test invoke of get container +// method of NeoFS Container contract. +func (c *Client) Get(args GetArgs) (*GetValues, error) { + prms, err := c.client.TestInvoke( + c.getMethod, + args.cid, + ) + if err != nil { + return nil, errors.Wrapf(err, "could not perform test invocation (%s)", c.getMethod) + } else if ln := len(prms); ln != 1 { + return nil, errors.Errorf("unexpected stack item count (%s): %d", c.getMethod, ln) + } + + cnrBytes, err := client.BytesFromStackParameter(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get byte array from stack item (%s)", c.getMethod) + } + + return &GetValues{ + cnr: cnrBytes, + }, nil +} diff --git a/pkg/morph/client/container/list.go b/pkg/morph/client/container/list.go new file mode 100644 index 000000000..4ea6dde2f --- /dev/null +++ b/pkg/morph/client/container/list.go @@ -0,0 +1,70 @@ +package container + +import ( + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/pkg/errors" +) + +// ListArgs groups the arguments +// of list containers test invoke call. +type ListArgs struct { + ownerID []byte // container owner identifier +} + +// ListValues groups the stack parameters +// returned by list containers test invoke. +type ListValues struct { + cidList [][]byte // list of container identifiers +} + +// SetOwnerID sets the container owner identifier +// in a binary format. +func (l *ListArgs) SetOwnerID(v []byte) { + l.ownerID = v +} + +// CIDList returns the list of container +// identifiers in a binary format. +func (l *ListValues) CIDList() [][]byte { + return l.cidList +} + +// List performs the test invoke of list container +// method of NeoFS Container contract. +func (c *Client) List(args ListArgs) (*ListValues, error) { + invokeArgs := make([]interface{}, 0, 1) + + if len(args.ownerID) > 0 { + invokeArgs = append(invokeArgs, args.ownerID) + } + + prms, err := c.client.TestInvoke( + c.listMethod, + invokeArgs..., + ) + if err != nil { + return nil, errors.Wrapf(err, "could not perform test invocation (%s)", c.listMethod) + } else if ln := len(prms); ln != 1 { + return nil, errors.Errorf("unexpected stack item count (%s): %d", c.listMethod, ln) + } + + prms, err = client.ArrayFromStackParameter(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get stack item array from stack item (%s)", c.listMethod) + } + + res := &ListValues{ + cidList: make([][]byte, 0, len(prms)), + } + + for i := range prms { + cid, err := client.BytesFromStackParameter(prms[i]) + if err != nil { + return nil, errors.Wrapf(err, "could not get byte array from stack item (%s)", c.listMethod) + } + + res.cidList = append(res.cidList, cid) + } + + return res, nil +} diff --git a/pkg/morph/client/container/put.go b/pkg/morph/client/container/put.go new file mode 100644 index 000000000..45f9b5fb7 --- /dev/null +++ b/pkg/morph/client/container/put.go @@ -0,0 +1,44 @@ +package container + +import ( + "github.com/pkg/errors" +) + +// PutArgs groups the arguments +// of put container invocation call. +type PutArgs struct { + ownerID []byte // container owner identifier + + cnr []byte // container in a binary format + + sig []byte // binary container signature +} + +// SetOwnerID sets the container owner identifier +// in a binary format. +func (p *PutArgs) SetOwnerID(v []byte) { + p.ownerID = v +} + +// SetContainer sets the container structure +// in a binary format. +func (p *PutArgs) SetContainer(v []byte) { + p.cnr = v +} + +// SetSignature sets the container structure +// owner's signature. +func (p *PutArgs) SetSignature(v []byte) { + p.sig = v +} + +// Put invokes the call of put container method +// of NeoFS Container contract. +func (c *Client) Put(args PutArgs) error { + return errors.Wrapf(c.client.Invoke( + c.putMethod, + args.ownerID, + args.cnr, + args.sig, + ), "could not invoke method (%s)", c.putMethod) +} diff --git a/pkg/morph/client/container/wrapper/container.go b/pkg/morph/client/container/wrapper/container.go new file mode 100644 index 000000000..5a390b401 --- /dev/null +++ b/pkg/morph/client/container/wrapper/container.go @@ -0,0 +1,148 @@ +package wrapper + +import ( + "github.com/nspcc-dev/neofs-api-go/refs" + "github.com/nspcc-dev/neofs-node/pkg/core/container" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" + "github.com/pkg/errors" +) + +// OwnerID represents the container owner identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/storage.OwnerID. +type OwnerID = storage.OwnerID + +// Container represents the NeoFS Container structure. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/storage.Container. +type Container = storage.Container + +// Put saves passed container structure in NeoFS system +// through Container contract call. +// +// Returns calculated container identifier and any error +// encountered that caused the saving to interrupt. +func (w *Wrapper) Put(cnr *Container) (*CID, error) { + // calculate container identifier + // + // Note: cid is used as return value only, but the calculation is performed + // primarily in order to catch potential error before contract client call. + cid, err := container.CalculateID(cnr) + if err != nil { + return nil, errors.Wrap(err, "could not calculate container identifier") + } + + // marshal the container + cnrBytes, err := cnr.MarshalBinary() + if err != nil { + return nil, errors.Wrap(err, "could not marshal the container") + } + + // prepare invocation arguments + args := contract.PutArgs{} + args.SetOwnerID(cnr.OwnerID().Bytes()) + args.SetContainer(cnrBytes) + args.SetSignature(nil) // TODO: set signature from request when will appear. + + // invoke smart contract call + if err := w.client.Put(args); err != nil { + return nil, errors.Wrap(err, "could not invoke smart contract") + } + + return cid, nil +} + +// Get reads the container from NeoFS system by identifier +// through Container contract call. +// +// If an empty slice is returned for the requested identifier, +// storage.ErrNotFound error is returned. +func (w *Wrapper) Get(cid CID) (*Container, error) { + // prepare invocation arguments + args := contract.GetArgs{} + args.SetCID(cid.Bytes()) + + // invoke smart contract call + values, err := w.client.Get(args) + if err != nil { + return nil, errors.Wrap(err, "could not invoke smart contract") + } + + cnrBytes := values.Container() + if len(cnrBytes) == 0 { + return nil, storage.ErrNotFound + } + + cnr := new(Container) + + // unmarshal the container + if err := cnr.UnmarshalBinary(cnrBytes); err != nil { + return nil, errors.Wrap(err, "could not unmarshal container") + } + + return cnr, nil +} + +// Delete removes the container from NeoFS system +// through Container contract call. +// +// Returns any error encountered that caused +// the removal to interrupt. +func (w *Wrapper) Delete(cid CID) error { + // prepare invocation arguments + args := contract.DeleteArgs{} + args.SetCID(cid.Bytes()) + args.SetOwnerID(nil) // TODO: add owner ID when will appear. + args.SetSignature(nil) // TODO: add CID signature when will appear. + + // invoke smart contract call + // + // Note: errors.Wrap return nil on nil error arg. + return errors.Wrap( + w.client.Delete(args), + "could not invoke smart contract", + ) +} + +// List returns a list of container identifiers belonging +// to the specified owner of NeoFS system. The list is composed +// through Container contract call. +// +// Returns the identifiers of all NeoFS containers if pointer +// to owner identifier is nil. +func (w *Wrapper) List(ownerID *OwnerID) ([]CID, error) { + // prepare invocation arguments + args := contract.ListArgs{} + + // Note: by default owner identifier slice is nil, + // so client won't attach invocation arguments. + // This behavior matches the nil argument of current method. + // If argument is not nil, we must specify owner identifier. + if ownerID != nil { + args.SetOwnerID(ownerID.Bytes()) + } + + // invoke smart contract call + values, err := w.client.List(args) + if err != nil { + return nil, errors.Wrap(err, "could not invoke smart contract") + } + + binCIDList := values.CIDList() + cidList := make([]CID, 0, len(binCIDList)) + + // unmarshal all container identifiers + for i := range binCIDList { + cid, err := refs.CIDFromBytes(binCIDList[i]) + if err != nil { + return nil, errors.Wrapf(err, "could not decode container ID #%d", i) + } + + cidList = append(cidList, cid) + } + + return cidList, nil +} diff --git a/pkg/morph/client/container/wrapper/eacl.go b/pkg/morph/client/container/wrapper/eacl.go new file mode 100644 index 000000000..4300186aa --- /dev/null +++ b/pkg/morph/client/container/wrapper/eacl.go @@ -0,0 +1,51 @@ +package wrapper + +import ( + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" + "github.com/pkg/errors" +) + +// Table represents extended ACL rule table. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage.Table. +type Table = storage.Table + +// GetEACL reads the extended ACL table from NeoFS system +// through Container contract call. +func (w *Wrapper) GetEACL(cid CID) (Table, error) { + // prepare invocation arguments + args := contract.EACLArgs{} + args.SetCID(cid.Bytes()) + + // invoke smart contract call + values, err := w.client.EACL(args) + if err != nil { + return nil, errors.Wrap(err, "could not invoke smart contract") + } + + // unmarshal and return eACL table + return eacl.UnmarshalTable(values.EACL()) +} + +// PutEACL saves the extended ACL table in NeoFS system +// through Container contract call. +// +// Returns any error encountered that caused the saving to interrupt. +func (w *Wrapper) PutEACL(cid CID, table Table, sig []byte) error { + // prepare invocation arguments + args := contract.SetEACLArgs{} + args.SetEACL(eacl.MarshalTable(table)) + args.SetCID(cid.Bytes()) + args.SetSignature(sig) + + // invoke smart contract call + // + // Note: errors.Wrap return nil on nil error arg. + return errors.Wrap( + w.client.SetEACL(args), + "could not invoke smart contract", + ) +} diff --git a/pkg/morph/client/container/wrapper/wrapper.go b/pkg/morph/client/container/wrapper/wrapper.go new file mode 100644 index 000000000..9d44c41ed --- /dev/null +++ b/pkg/morph/client/container/wrapper/wrapper.go @@ -0,0 +1,43 @@ +package wrapper + +import ( + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" +) + +// Client represents the Container contract client. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/morph/client/container.Client. +type Client = container.Client + +// CID represents the container identifier. +// +// CID is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/storage.CID. +type CID = storage.CID + +// Wrapper is a wrapper over container contract +// client which implements container storage and +// eACL storage methods. +// +// Working wrapper must be created via constructor New. +// Using the Wrapper that has been created with new(Wrapper) +// expression (or just declaring a Wrapper variable) is unsafe +// and can lead to panic. +type Wrapper struct { + client *Client +} + +// New creates, initializes and returns the Wrapper instance. +// +// If Client is nil, container.ErrNilClient is returned. +func New(c *Client) (*Wrapper, error) { + if c == nil { + return nil, container.ErrNilClient + } + + return &Wrapper{ + client: c, + }, nil +} diff --git a/pkg/morph/client/netmap/add_peer.go b/pkg/morph/client/netmap/add_peer.go new file mode 100644 index 000000000..60eef92b5 --- /dev/null +++ b/pkg/morph/client/netmap/add_peer.go @@ -0,0 +1,104 @@ +package netmap + +import ( + "github.com/pkg/errors" +) + +// PeerInfo groups the parameters of +// new NeoFS peer. +type PeerInfo struct { + address []byte // peer network address in a binary format + + key []byte // peer public key + + opts [][]byte // binary peer options +} + +// AddPeerArgs groups the arguments +// of add peer invocation call. +type AddPeerArgs struct { + info PeerInfo // peer information +} + +const addPeerFixedArgNumber = 2 + +// Address returns the peer network address +// in a binary format. +// +// Address format is dictated by application +// architecture. +func (a PeerInfo) Address() []byte { + return a.address +} + +// SetAddress sets the peer network address +// in a binary format. +// +// Address format is dictated by application +// architecture. +func (a *PeerInfo) SetAddress(v []byte) { + a.address = v +} + +// PublicKey returns the peer public key +// in a binary format. +// +// Key format is dictated by application +// architecture. +func (a PeerInfo) PublicKey() []byte { + return a.key +} + +// SetPublicKey sets the peer public key +// in a binary format. +// +// Key format is dictated by application +// architecture. +func (a *PeerInfo) SetPublicKey(v []byte) { + a.key = v +} + +// Options returns the peer options +// in a binary format. +// +// Option format is dictated by application +// architecture. +func (a PeerInfo) Options() [][]byte { + return a.opts +} + +// SetOptions sets the peer options +// in a binary format. +// +// Option format is dictated by application +// architecture. +func (a *PeerInfo) SetOptions(v [][]byte) { + a.opts = v +} + +// SetInfo sets the peer information. +func (a *AddPeerArgs) SetInfo(v PeerInfo) { + a.info = v +} + +// AddPeer invokes the call of add peer method +// of NeoFS Netmap contract. +func (c *Client) AddPeer(args AddPeerArgs) error { + info := args.info + + invokeArgs := make([]interface{}, 0, addPeerFixedArgNumber+len(info.opts)) + + invokeArgs = append(invokeArgs, + info.address, + info.key, + ) + + for i := range info.opts { + invokeArgs = append(invokeArgs, info.opts[i]) + } + + return errors.Wrapf(c.client.Invoke( + c.addPeerMethod, + invokeArgs..., + ), "could not invoke method (%s)", c.addPeerMethod) +} diff --git a/pkg/morph/client/netmap/client.go b/pkg/morph/client/netmap/client.go new file mode 100644 index 000000000..ab33ea11b --- /dev/null +++ b/pkg/morph/client/netmap/client.go @@ -0,0 +1,156 @@ +package netmap + +import ( + "errors" + + "github.com/nspcc-dev/neofs-node/pkg/morph/client" +) + +// Client is a wrapper over StaticClient +// which makes calls with the names and arguments +// of the NeoFS Netmap contract. +// +// Working client must be created via constructor New. +// Using the Client that has been created with new(Client) +// expression (or just declaring a Client variable) is unsafe +// and can lead to panic. +type Client struct { + client *client.StaticClient // static Netmap contract client + + *cfg // contract method names +} + +// ErrNilClient is returned by functions that expect +// a non-nil Client pointer, but received nil. +var ErrNilClient = errors.New("netmap contract client is nil") + +// Option is a client configuration change function. +type Option func(*cfg) + +type cfg struct { + addPeerMethod, // add peer method name for invocation + newEpochMethod, // new epoch method name for invocation + netMapMethod, // get network map method name + updateStateMethod, // update state method name for invocation + innerRingListMethod string // IR list method name for invocation +} + +const ( + defaultAddPeerMethod = "AddPeer" // default add peer method name + defaultNewEpochMethod = "NewEpoch" // default new epoch method name + defaultNetMapMethod = "Netmap" // default get network map method name + defaultUpdateStateMethod = "UpdateState" // default update state method name + defaultInnerRIngListMethod = "InnerRingList" // default IR list method name +) + +func defaultConfig() *cfg { + return &cfg{ + addPeerMethod: defaultAddPeerMethod, + newEpochMethod: defaultNewEpochMethod, + netMapMethod: defaultNetMapMethod, + updateStateMethod: defaultUpdateStateMethod, + innerRingListMethod: defaultInnerRIngListMethod, + } +} + +// New creates, initializes and returns the Client instance. +// +// If StaticClient is nil, client.ErrNilStaticClient is returned. +// +// Other values are set according to provided options, or by default: +// * add peer method name: AddPeer; +// * new epoch method name: NewEpoch; +// * get network map method name: Netmap; +// * update state method name: UpdateState; +// * inner ring list method name: InnerRingList. +// +// If desired option satisfies the default value, it can be omitted. +// If multiple options of the same config value are supplied, +// the option with the highest index in the arguments will be used. +func New(c *client.StaticClient, opts ...Option) (*Client, error) { + if c == nil { + return nil, client.ErrNilStaticClient + } + + res := &Client{ + client: c, + cfg: defaultConfig(), // build default configuration + } + + // apply options + for _, opt := range opts { + opt(res.cfg) + } + + return res, nil +} + +// WithAddPeerMethod returns a client constructor option that +// specifies the method name of adding peer operation. +// +// Ignores empty value. +// +// If option not provided, "AddPeer" is used. +func WithAddPeerMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.addPeerMethod = n + } + } +} + +// WithNewEpochMethod returns a client constructor option that +// specifies the method name of new epoch operation. +// +// Ignores empty value. +// +// If option not provided, "NewEpoch" is used. +func WithNewEpochMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.newEpochMethod = n + } + } +} + +// WithNetMapMethod returns a client constructor option that +// specifies the method name of network map receiving operation. +// +// Ignores empty value. +// +// If option not provided, "Netmap" is used. +func WithNetMapMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.netMapMethod = n + } + } +} + +// WithUpdateStateMethod returns a client constructor option that +// specifies the method name of peer state updating operation. +// +// Ignores empty value. +// +// If option not provided, "UpdateState" is used. +func WithUpdateStateMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.updateStateMethod = n + } + } +} + +// WithInnerRingListMethod returns a client constructor option that +// specifies the method name of inner ring listing operation. +// +// Ignores empty value. +// +// If option not provided, "InnerRingList" is used. +func WithInnerRingListMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.innerRingListMethod = n + } + } +} diff --git a/pkg/morph/client/netmap/ir_list.go b/pkg/morph/client/netmap/ir_list.go new file mode 100644 index 000000000..035c0553f --- /dev/null +++ b/pkg/morph/client/netmap/ir_list.go @@ -0,0 +1,61 @@ +package netmap + +import ( + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/pkg/errors" +) + +// InnerRingListArgs groups the arguments +// of inner ring list test invoke call. +type InnerRingListArgs struct { +} + +// InnerRingListValues groups the stack parameters +// returned by inner ring list test invoke. +type InnerRingListValues struct { + keys [][]byte // list of keys of IR nodes in a binary format +} + +// KeyList return the list of IR node keys +// in a binary format. +func (g InnerRingListValues) KeyList() [][]byte { + return g.keys +} + +// InnerRingList performs the test invoke of inner ring list +// method of NeoFS Netmap contract. +func (c *Client) InnerRingList(args InnerRingListArgs) (*InnerRingListValues, error) { + prms, err := c.client.TestInvoke( + c.innerRingListMethod, + ) + if err != nil { + return nil, errors.Wrapf(err, "could not perform test invocation (%s)", c.innerRingListMethod) + } else if ln := len(prms); ln != 1 { + return nil, errors.Errorf("unexpected stack item count (%s): %d", c.innerRingListMethod, ln) + } + + prms, err = client.ArrayFromStackParameter(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get stack item array from stack item (%s)", c.innerRingListMethod) + } + + res := &InnerRingListValues{ + keys: make([][]byte, 0, len(prms)), + } + + for i := range prms { + nodePrms, err := client.ArrayFromStackParameter(prms[i]) + if err != nil { + return nil, errors.Wrap(err, "could not get stack item array (Node #%d)") + } + + key, err := client.BytesFromStackParameter(nodePrms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not parse stack item (Key #%d)", i) + } + + res.keys = append(res.keys, key) + } + + return res, nil +} diff --git a/pkg/morph/client/netmap/netmap.go b/pkg/morph/client/netmap/netmap.go new file mode 100644 index 000000000..8efb4238a --- /dev/null +++ b/pkg/morph/client/netmap/netmap.go @@ -0,0 +1,99 @@ +package netmap + +import ( + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/pkg/errors" +) + +// GetNetMapArgs groups the arguments +// of get network map test invoke call. +type GetNetMapArgs struct { +} + +// GetNetMapValues groups the stack parameters +// returned by get network map test invoke. +type GetNetMapValues struct { + peers []PeerInfo // peer list in a binary format +} + +const nodeInfoFixedPrmNumber = 3 + +// Peers return the list of peers from +// network map in a binary format. +func (g GetNetMapValues) Peers() []PeerInfo { + return g.peers +} + +// NetMap performs the test invoke of get network map +// method of NeoFS Netmap contract. +func (c *Client) NetMap(args GetNetMapArgs) (*GetNetMapValues, error) { + prms, err := c.client.TestInvoke( + c.netMapMethod, + ) + if err != nil { + return nil, errors.Wrapf(err, "could not perform test invocation (%s)", c.netMapMethod) + } else if ln := len(prms); ln != 1 { + return nil, errors.Errorf("unexpected stack item count (%s): %d", c.netMapMethod, ln) + } + + prms, err = client.ArrayFromStackParameter(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get stack item array from stack item (%s)", c.netMapMethod) + } + + res := &GetNetMapValues{ + peers: make([]PeerInfo, 0, len(prms)), + } + + for i := range prms { + peer, err := peerInfoFromStackItem(prms[i]) + if err != nil { + return nil, errors.Wrapf(err, "could not parse stack item (Peer #%d)", i) + } + + res.peers = append(res.peers, *peer) + } + + return res, nil +} + +func peerInfoFromStackItem(prm smartcontract.Parameter) (*PeerInfo, error) { + prms, err := client.ArrayFromStackParameter(prm) + if err != nil { + return nil, errors.Wrapf(err, "could not get stack item array (PeerInfo)") + } else if ln := len(prms); ln != nodeInfoFixedPrmNumber { + return nil, errors.Errorf("unexpected stack item count (PeerInfo): expected %d, has %d", 3, ln) + } + + res := new(PeerInfo) + + // Address + res.address, err = client.BytesFromStackParameter(prms[0]) + if err != nil { + return nil, errors.Wrap(err, "could not get byte array from stack item (Address)") + } + + // Public key + if res.key, err = client.BytesFromStackParameter(prms[1]); err != nil { + return nil, errors.Wrap(err, "could not get byte array from stack item (Public key)") + } + + // Options + if prms, err = client.ArrayFromStackParameter(prms[2]); err != nil { + return nil, errors.Wrapf(err, "could not get stack item array (Options)") + } + + res.opts = make([][]byte, 0, len(prms)) + + for i := range prms { + opt, err := client.BytesFromStackParameter(prms[i]) + if err != nil { + return nil, errors.Wrapf(err, "could not get byte array from stack item (Option #%d)", i) + } + + res.opts = append(res.opts, opt) + } + + return res, nil +} diff --git a/pkg/morph/client/netmap/new_epoch.go b/pkg/morph/client/netmap/new_epoch.go new file mode 100644 index 000000000..671901792 --- /dev/null +++ b/pkg/morph/client/netmap/new_epoch.go @@ -0,0 +1,23 @@ +package netmap + +import "github.com/pkg/errors" + +// NewEpochArgs groups the arguments +// of new epoch invocation call. +type NewEpochArgs struct { + number int64 // new epoch number +} + +// SetEpochNumber sets the new epoch number. +func (a *NewEpochArgs) SetEpochNumber(v int64) { + a.number = v +} + +// NewEpoch invokes the call of new epoch method +// of NeoFS Netmap contract. +func (c *Client) NewEpoch(args NewEpochArgs) error { + return errors.Wrapf(c.client.Invoke( + c.addPeerMethod, + args.number, + ), "could not invoke method (%s)", c.newEpochMethod) +} diff --git a/pkg/morph/client/netmap/update_state.go b/pkg/morph/client/netmap/update_state.go new file mode 100644 index 000000000..2a825a304 --- /dev/null +++ b/pkg/morph/client/netmap/update_state.go @@ -0,0 +1,34 @@ +package netmap + +import ( + "github.com/pkg/errors" +) + +// UpdateStateArgs groups the arguments +// of update state invocation call. +type UpdateStateArgs struct { + key []byte // peer public key + + state int64 // new peer state +} + +// SetPublicKey sets peer public key +// in a binary format. +func (u *UpdateStateArgs) SetPublicKey(v []byte) { + u.key = v +} + +// SetState sets the new peer state. +func (u *UpdateStateArgs) SetState(v int64) { + u.state = v +} + +// UpdateState invokes the call of update state method +// of NeoFS Netmap contract. +func (c *Client) UpdateState(args UpdateStateArgs) error { + return errors.Wrapf(c.client.Invoke( + c.addPeerMethod, + args.key, + args.state, + ), "could not invoke method (%s)", c.updateStateMethod) +} diff --git a/pkg/morph/client/netmap/wrapper/add_peer.go b/pkg/morph/client/netmap/wrapper/add_peer.go new file mode 100644 index 000000000..4afcee702 --- /dev/null +++ b/pkg/morph/client/netmap/wrapper/add_peer.go @@ -0,0 +1,37 @@ +package wrapper + +import ( + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" + "github.com/pkg/errors" +) + +// AddPeer registers peer in NeoFS network through +// Netmap contract call. +func (w *Wrapper) AddPeer(nodeInfo netmap.Info) error { + // prepare invocation arguments + args := contract.AddPeerArgs{} + + info := contract.PeerInfo{} + info.SetPublicKey(nodeInfo.PublicKey()) + info.SetAddress([]byte(nodeInfo.Address())) + + opts := nodeInfo.Options() + binOpts := make([][]byte, 0, len(opts)) + + for i := range opts { + binOpts = append(binOpts, []byte(opts[i])) + } + + info.SetOptions(binOpts) + + args.SetInfo(info) + + // invoke smart contract call + // + // Note: errors.Wrap returns nil on nil error arg. + return errors.Wrap( + w.client.AddPeer(args), + "could not invoke smart contract", + ) +} diff --git a/pkg/morph/client/netmap/wrapper/irlist.go b/pkg/morph/client/netmap/wrapper/irlist.go new file mode 100644 index 000000000..e339d40e4 --- /dev/null +++ b/pkg/morph/client/netmap/wrapper/irlist.go @@ -0,0 +1,21 @@ +package wrapper + +import ( + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" + "github.com/pkg/errors" +) + +// InnerRingKeys receives public key list of inner +// ring nodes through Netmap contract call and returns it. +func (w *Wrapper) InnerRingKeys() ([][]byte, error) { + // prepare invocation arguments + args := contract.InnerRingListArgs{} + + // invoke smart contract call + values, err := w.client.InnerRingList(args) + if err != nil { + return nil, errors.Wrap(err, "could not invoke smart contract") + } + + return values.KeyList(), nil +} diff --git a/pkg/morph/client/netmap/wrapper/netmap.go b/pkg/morph/client/netmap/wrapper/netmap.go new file mode 100644 index 000000000..250ac145a --- /dev/null +++ b/pkg/morph/client/netmap/wrapper/netmap.go @@ -0,0 +1,60 @@ +package wrapper + +import ( + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" + "github.com/pkg/errors" +) + +// NetMap represents the NeoFS network map. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/NetMap. +type NetMap = netmap.NetMap + +// Info represents node information. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/netmap.Info. +type Info = netmap.Info + +// GetNetMap receives information list about storage nodes +// through the Netmap contract call, composes network map +// from them and returns it. +func (w *Wrapper) GetNetMap() (*NetMap, error) { + // prepare invocation arguments + args := contract.GetNetMapArgs{} + + // invoke smart contract call + values, err := w.client.NetMap(args) + if err != nil { + return nil, errors.Wrap(err, "could not invoke smart contract") + } + + // parse response and fill the network map + nm := netmap.New() + + peerList := values.Peers() + + for i := range peerList { + info := Info{} + + info.SetPublicKey(peerList[i].PublicKey()) + info.SetAddress(string(peerList[i].Address())) + + binOpts := peerList[i].Options() + opts := make([]string, 0, len(binOpts)) + + for j := range binOpts { + opts = append(opts, string(binOpts[j])) + } + + info.SetOptions(opts) + + if err := nm.AddNode(info); err != nil { + return nil, errors.Wrapf(err, "could not add node #%d to network map", i) + } + } + + return nm, nil +} diff --git a/pkg/morph/client/netmap/wrapper/new_epoch.go b/pkg/morph/client/netmap/wrapper/new_epoch.go new file mode 100644 index 000000000..d4eae6530 --- /dev/null +++ b/pkg/morph/client/netmap/wrapper/new_epoch.go @@ -0,0 +1,23 @@ +package wrapper + +import ( + "github.com/nspcc-dev/neofs-node/pkg/core/netmap/epoch" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" + "github.com/pkg/errors" +) + +// NewEpoch updates NeoFS epoch number through +// Netmap contract call. +func (w *Wrapper) NewEpoch(e epoch.Epoch) error { + // prepare invocation arguments + args := contract.NewEpochArgs{} + args.SetEpochNumber(int64(epoch.ToUint64(e))) + + // invoke smart contract call + // + // Note: errors.Wrap returns nil on nil error arg. + return errors.Wrap( + w.client.NewEpoch(args), + "could not invoke smart contract", + ) +} diff --git a/pkg/morph/client/netmap/wrapper/update_state.go b/pkg/morph/client/netmap/wrapper/update_state.go new file mode 100644 index 000000000..ad296f0fe --- /dev/null +++ b/pkg/morph/client/netmap/wrapper/update_state.go @@ -0,0 +1,32 @@ +package wrapper + +import ( + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" + "github.com/pkg/errors" +) + +// NodeState is a type of node states enumeration. +type NodeState int64 + +const ( + _ NodeState = iota + + // StateOffline is an offline node state value. + StateOffline +) + +// UpdatePeerState changes peer status through Netmap contract +// call. +func (w *Wrapper) UpdatePeerState(key []byte, state NodeState) error { + args := contract.UpdateStateArgs{} + args.SetPublicKey(key) + args.SetState(int64(state)) + + // invoke smart contract call + // + // Note: errors.Wrap returns nil on nil error arg. + return errors.Wrap( + w.client.UpdateState(args), + "could not invoke smart contract", + ) +} diff --git a/pkg/morph/client/netmap/wrapper/wrapper.go b/pkg/morph/client/netmap/wrapper/wrapper.go new file mode 100644 index 000000000..5a8eabf75 --- /dev/null +++ b/pkg/morph/client/netmap/wrapper/wrapper.go @@ -0,0 +1,43 @@ +package wrapper + +import ( + "errors" + + "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" +) + +// Client represents the Netmap contract client. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap.Client. +type Client = netmap.Client + +// Wrapper is a wrapper over netmap contract +// client which implements: +// * network map storage; +// * tool for peer state updating. +// +// Working wrapper must be created via constructor New. +// Using the Wrapper that has been created with new(Wrapper) +// expression (or just declaring a Wrapper variable) is unsafe +// and can lead to panic. +type Wrapper struct { + client *Client +} + +// ErrNilWrapper is returned by functions that expect +// a non-nil Wrapper pointer, but received nil. +var ErrNilWrapper = errors.New("netmap contract client wrapper is nil") + +// New creates, initializes and returns the Wrapper instance. +// +// If Client is nil, netmap.ErrNilClient is returned. +func New(c *Client) (*Wrapper, error) { + if c == nil { + return nil, netmap.ErrNilClient + } + + return &Wrapper{ + client: c, + }, nil +} diff --git a/pkg/morph/client/static.go b/pkg/morph/client/static.go new file mode 100644 index 000000000..c903b3af9 --- /dev/null +++ b/pkg/morph/client/static.go @@ -0,0 +1,62 @@ +package client + +import ( + "errors" + + sc "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// StaticClient is a wrapper over Neo:Morph client +// that invokes single smart contract methods with fixed fee. +// +// Working static client must be created via constructor NewStatic. +// Using the StaticClient that has been created with new(StaticClient) +// expression (or just declaring a StaticClient variable) is unsafe +// and can lead to panic. +type StaticClient struct { + client *Client // neo-go client instance + + scScriptHash util.Uint160 // contract script-hash + + fee util.Fixed8 // invocation fee +} + +// ErrNilStaticClient is returned by functions that expect +// a non-nil StaticClient pointer, but received nil. +var ErrNilStaticClient = errors.New("static client is nil") + +// NewStatic creates, initializes and returns the StaticClient instance. +// +// If provided Client instance is nil, ErrNilClient is returned. +func NewStatic(client *Client, scriptHash util.Uint160, fee util.Fixed8) (*StaticClient, error) { + if client == nil { + return nil, ErrNilClient + } + + return &StaticClient{ + client: client, + scScriptHash: scriptHash, + fee: fee, + }, nil +} + +// Invoke calls Invoke method of Client with static internal script hash and fee. +// Supported args types are the same as in Client. +func (s StaticClient) Invoke(method string, args ...interface{}) error { + return s.client.Invoke( + s.scScriptHash, + s.fee, + method, + args..., + ) +} + +// TestInvoke calls TestInvoke method of Client with static internal script hash. +func (s StaticClient) TestInvoke(method string, args ...interface{}) ([]sc.Parameter, error) { + return s.client.TestInvoke( + s.scScriptHash, + method, + args..., + ) +} diff --git a/lib/blockchain/goclient/util.go b/pkg/morph/client/util.go similarity index 91% rename from lib/blockchain/goclient/util.go rename to pkg/morph/client/util.go index 82e30f49b..2fec25f50 100644 --- a/lib/blockchain/goclient/util.go +++ b/pkg/morph/client/util.go @@ -1,10 +1,9 @@ -package goclient +package client import ( "encoding/binary" sc "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/util" "github.com/pkg/errors" ) @@ -71,6 +70,10 @@ func IntFromStackParameter(param sc.Parameter) (int64, error) { // BytesFromStackParameter receives binary value from the value of a smart contract parameter. func BytesFromStackParameter(param sc.Parameter) ([]byte, error) { if param.Type != sc.ByteArrayType { + if param.Type == sc.AnyType && param.Value == nil { + return nil, nil + } + return nil, errors.Errorf("chain/client: %s is not a byte array type", param.Type) } @@ -123,9 +126,3 @@ func StringFromStackParameter(param sc.Parameter) (string, error) { return "", errors.Errorf("chain/client: %s is not a string type", param.Type) } } - -// ReadStorage of the contract directly. Use it for debug, try to obtain -// smart-contract data from contract method with TestInvoke function. -func ReadStorage(c *Client, contract util.Uint160, key []byte) ([]byte, error) { - return c.cli.GetStorageByHash(contract, key) -} diff --git a/lib/blockchain/goclient/util_test.go b/pkg/morph/client/util_test.go similarity index 99% rename from lib/blockchain/goclient/util_test.go rename to pkg/morph/client/util_test.go index 5752e2dda..37899f2f7 100644 --- a/lib/blockchain/goclient/util_test.go +++ b/pkg/morph/client/util_test.go @@ -1,4 +1,4 @@ -package goclient +package client import ( "testing" diff --git a/pkg/morph/event/balance/lock.go b/pkg/morph/event/balance/lock.go new file mode 100644 index 000000000..39ab6b1f2 --- /dev/null +++ b/pkg/morph/event/balance/lock.go @@ -0,0 +1,90 @@ +package balance + +import ( + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/pkg/errors" +) + +// Lock structure of balance.Lock notification from morph chain. +type Lock struct { + id []byte + user util.Uint160 + lock util.Uint160 + amount int64 // Fixed16 + until int64 +} + +// MorphEvent implements Neo:Morph Event interface. +func (Lock) MorphEvent() {} + +// ID is a withdraw transaction hash. +func (l Lock) ID() []byte { return l.id } + +// User returns withdraw receiver script hash from main net. +func (l Lock) User() util.Uint160 { return l.user } + +// LockAccount return script hash for balance contract wallet. +func (l Lock) LockAccount() util.Uint160 { return l.lock } + +// Amount of the locked assets. +func (l Lock) Amount() int64 { return l.amount } + +// Until is a epoch before locked account exists. +func (l Lock) Until() int64 { return l.until } + +// ParseLock from notification into lock structure. +func ParseLock(params []smartcontract.Parameter) (event.Event, error) { + var ( + ev Lock + err error + ) + + if ln := len(params); ln != 5 { + return nil, event.WrongNumberOfParameters(5, ln) + } + + // parse id + ev.id, err = client.BytesFromStackParameter(params[0]) + if err != nil { + return nil, errors.Wrap(err, "could not get lock id") + } + + // parse user + user, err := client.BytesFromStackParameter(params[1]) + if err != nil { + return nil, errors.Wrap(err, "could not get lock user value") + } + + ev.user, err = util.Uint160DecodeBytesBE(user) + if err != nil { + return nil, errors.Wrap(err, "could not convert lock user value to uint160") + } + + // parse lock account + lock, err := client.BytesFromStackParameter(params[2]) + if err != nil { + return nil, errors.Wrap(err, "could not get lock account value") + } + + ev.lock, err = util.Uint160DecodeBytesBE(lock) + if err != nil { + return nil, errors.Wrap(err, "could not convert lock account value to uint160") + } + + // parse amount + ev.amount, err = client.IntFromStackParameter(params[3]) + if err != nil { + return nil, errors.Wrap(err, "could not get lock amount") + } + + // parse until deadline + ev.until, err = client.IntFromStackParameter(params[4]) + if err != nil { + return nil, errors.Wrap(err, "could not get lock deadline") + } + + return ev, nil +} diff --git a/pkg/morph/event/balance/lock_test.go b/pkg/morph/event/balance/lock_test.go new file mode 100644 index 000000000..28aedc5ce --- /dev/null +++ b/pkg/morph/event/balance/lock_test.go @@ -0,0 +1,155 @@ +package balance + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/stretchr/testify/require" +) + +func TestParseLock(t *testing.T) { + var ( + id = []byte("Hello World") + user = util.Uint160{0x1, 0x2, 0x3} + lock = util.Uint160{0x3, 0x2, 0x1} + + amount int64 = 10 + until int64 = 20 + ) + + t.Run("wrong number of parameters", func(t *testing.T) { + prms := []smartcontract.Parameter{ + {}, + {}, + } + + _, err := ParseLock(prms) + require.EqualError(t, err, event.WrongNumberOfParameters(5, len(prms)).Error()) + }) + + t.Run("wrong id parameter", func(t *testing.T) { + _, err := ParseLock([]smartcontract.Parameter{ + { + Type: smartcontract.IntegerType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong from parameter", func(t *testing.T) { + _, err := ParseLock([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + { + Type: smartcontract.IntegerType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong lock parameter", func(t *testing.T) { + _, err := ParseLock([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong amount parameter", func(t *testing.T) { + _, err := ParseLock([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.ByteArrayType, + Value: lock.BytesBE(), + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong until parameter", func(t *testing.T) { + _, err := ParseLock([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.ByteArrayType, + Value: lock.BytesBE(), + }, + { + Type: smartcontract.IntegerType, + Value: amount, + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("correct behavior", func(t *testing.T) { + ev, err := ParseLock([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.ByteArrayType, + Value: lock.BytesBE(), + }, + { + Type: smartcontract.IntegerType, + Value: amount, + }, + { + Type: smartcontract.IntegerType, + Value: until, + }, + }) + + require.NoError(t, err) + require.Equal(t, Lock{ + id: id, + user: user, + lock: lock, + amount: amount, + until: until, + }, ev) + }) +} diff --git a/lib/blockchain/event/event.go b/pkg/morph/event/event.go similarity index 100% rename from lib/blockchain/event/event.go rename to pkg/morph/event/event.go diff --git a/lib/blockchain/event/handler.go b/pkg/morph/event/handler.go similarity index 82% rename from lib/blockchain/event/handler.go rename to pkg/morph/event/handler.go index 2d9c5b774..433880ffd 100644 --- a/lib/blockchain/event/handler.go +++ b/pkg/morph/event/handler.go @@ -17,6 +17,7 @@ func (s *HandlerInfo) SetHandler(v Handler) { s.h = v } -func (s HandlerInfo) handler() Handler { +// Handler returns an event handler. +func (s HandlerInfo) Handler() Handler { return s.h } diff --git a/lib/blockchain/event/listener.go b/pkg/morph/event/listener.go similarity index 86% rename from lib/blockchain/event/listener.go rename to pkg/morph/event/listener.go index 2dcfceb3c..a2fec83d3 100644 --- a/lib/blockchain/event/listener.go +++ b/pkg/morph/event/listener.go @@ -6,9 +6,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/blockchain/goclient" - "github.com/nspcc-dev/neofs-node/lib/blockchain/subscriber" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/subscriber" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -35,6 +34,9 @@ type Listener interface { // // Must ignore nil handlers. RegisterHandler(HandlerInfo) + + // Must stop the event listener. + Stop() } // ListenerParams is a group of parameters @@ -61,12 +63,12 @@ type listener struct { subscriber subscriber.Subscriber } -const ( - newListenerFailMsg = "could not instantiate Listener" +const newListenerFailMsg = "could not instantiate Listener" - errNilLogger = internal.Error("nil logger") +var ( + errNilLogger = errors.New("nil logger") - errNilSubscriber = internal.Error("nil event subscriber") + errNilSubscriber = errors.New("nil event subscriber") ) // Listen starts the listening for events with registered handlers. @@ -91,7 +93,7 @@ func (s listener) listen(ctx context.Context) error { // fill the list with the contracts with set event parsers. s.mtx.RLock() for hashType := range s.parsers { - scHash := hashType.scriptHash() + scHash := hashType.ScriptHash() // prevent repetitions for _, hash := range hashes { @@ -100,7 +102,7 @@ func (s listener) listen(ctx context.Context) error { } } - hashes = append(hashes, hashType.scriptHash()) + hashes = append(hashes, hashType.ScriptHash()) } // mark listener as started @@ -123,8 +125,8 @@ loop: for { select { case <-ctx.Done(): - s.log.Warn("stop event listener by context", - zap.String("error", ctx.Err().Error()), + s.log.Info("stop event listener by context", + zap.String("reason", ctx.Err().Error()), ) break loop case notifyEvent, ok := <-chEvent: @@ -147,7 +149,7 @@ func (s listener) parseAndHandle(notifyEvent *result.NotificationEvent) { ) // stack item must be an array of items - arr, err := goclient.ArrayFromStackParameter(notifyEvent.Item) + arr, err := client.ArrayFromStackParameter(notifyEvent.Item) if err != nil { log.Warn("stack item is not an array type", zap.String("error", err.Error()), @@ -160,7 +162,7 @@ func (s listener) parseAndHandle(notifyEvent *result.NotificationEvent) { } // first item must be a byte array - typBytes, err := goclient.BytesFromStackParameter(arr[0]) + typBytes, err := client.BytesFromStackParameter(arr[0]) if err != nil { log.Warn("first array item is not a byte array", zap.String("error", err.Error()), @@ -225,7 +227,7 @@ func (s listener) parseAndHandle(notifyEvent *result.NotificationEvent) { // Ignores the parser if listener is started. func (s listener) SetParser(p ParserInfo) { log := s.log.With( - zap.String("script hash LE", p.scriptHash().StringLE()), + zap.String("script hash LE", p.ScriptHash().StringLE()), zap.Stringer("event type", p.getType()), ) @@ -258,11 +260,11 @@ func (s listener) SetParser(p ParserInfo) { // Ignores handlers of event without parser. func (s listener) RegisterHandler(p HandlerInfo) { log := s.log.With( - zap.String("script hash LE", p.scriptHash().StringLE()), - zap.Stringer("event type", p.getType()), + zap.String("script hash LE", p.ScriptHash().StringLE()), + zap.Stringer("event type", p.GetType()), ) - handler := p.handler() + handler := p.Handler() if handler == nil { log.Warn("ignore nil event handler") return @@ -282,13 +284,18 @@ func (s listener) RegisterHandler(p HandlerInfo) { s.mtx.Lock() s.handlers[p.scriptHashWithType] = append( s.handlers[p.scriptHashWithType], - p.handler(), + p.Handler(), ) s.mtx.Unlock() log.Info("registered new event handler") } +// Stop closes subscription channel with remote neo node. +func (s listener) Stop() { + s.subscriber.Close() +} + // NewListener create the notification event listener instance and returns Listener interface. func NewListener(p ListenerParams) (Listener, error) { switch { diff --git a/pkg/morph/event/neofs/cheque.go b/pkg/morph/event/neofs/cheque.go new file mode 100644 index 000000000..6374392dd --- /dev/null +++ b/pkg/morph/event/neofs/cheque.go @@ -0,0 +1,80 @@ +package neofs + +import ( + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/pkg/errors" +) + +// Cheque structure of neofs.Cheque notification from mainnet chain. +type Cheque struct { + id []byte + amount int64 // Fixed8 + user util.Uint160 + lock util.Uint160 +} + +// MorphEvent implements Neo:Morph Event interface. +func (Cheque) MorphEvent() {} + +// ID is a withdraw transaction hash. +func (c Cheque) ID() []byte { return c.id } + +// User returns withdraw receiver script hash from main net. +func (c Cheque) User() util.Uint160 { return c.user } + +// Amount of the sent assets. +func (c Cheque) Amount() int64 { return c.amount } + +// LockAccount return script hash for balance contract wallet. +func (c Cheque) LockAccount() util.Uint160 { return c.lock } + +// ParseCheque from notification into cheque structure. +func ParseCheque(params []smartcontract.Parameter) (event.Event, error) { + var ( + ev Cheque + err error + ) + + if ln := len(params); ln != 4 { + return nil, event.WrongNumberOfParameters(4, ln) + } + + // parse id + ev.id, err = client.BytesFromStackParameter(params[0]) + if err != nil { + return nil, errors.Wrap(err, "could not get cheque id") + } + + // parse user + user, err := client.BytesFromStackParameter(params[1]) + if err != nil { + return nil, errors.Wrap(err, "could not get cheque user") + } + + ev.user, err = util.Uint160DecodeBytesBE(user) + if err != nil { + return nil, errors.Wrap(err, "could not convert cheque user to uint160") + } + + // parse amount + ev.amount, err = client.IntFromStackParameter(params[2]) + if err != nil { + return nil, errors.Wrap(err, "could not get cheque amount") + } + + // parse lock account + lock, err := client.BytesFromStackParameter(params[3]) + if err != nil { + return nil, errors.Wrap(err, "could not get cheque lock account") + } + + ev.lock, err = util.Uint160DecodeBytesBE(lock) + if err != nil { + return nil, errors.Wrap(err, "could not convert cheque lock account to uint160") + } + + return ev, nil +} diff --git a/pkg/morph/event/neofs/cheque_test.go b/pkg/morph/event/neofs/cheque_test.go new file mode 100644 index 000000000..f0ff71ee0 --- /dev/null +++ b/pkg/morph/event/neofs/cheque_test.go @@ -0,0 +1,123 @@ +package neofs + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/stretchr/testify/require" +) + +func TestParseCheque(t *testing.T) { + var ( + id = []byte("Hello World") + user = util.Uint160{0x1, 0x2, 0x3} + lock = util.Uint160{0x3, 0x2, 0x1} + + amount int64 = 10 + ) + + t.Run("wrong number of parameters", func(t *testing.T) { + prms := []smartcontract.Parameter{ + {}, + {}, + } + + _, err := ParseCheque(prms) + require.EqualError(t, err, event.WrongNumberOfParameters(4, len(prms)).Error()) + }) + + t.Run("wrong id parameter", func(t *testing.T) { + _, err := ParseCheque([]smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong user parameter", func(t *testing.T) { + _, err := ParseCheque([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong amount parameter", func(t *testing.T) { + _, err := ParseCheque([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong lock parameter", func(t *testing.T) { + _, err := ParseCheque([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.IntegerType, + Value: amount, + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("correct behavior", func(t *testing.T) { + ev, err := ParseCheque([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.IntegerType, + Value: amount, + }, + { + Type: smartcontract.ByteArrayType, + Value: lock.BytesBE(), + }, + }) + + require.NoError(t, err) + require.Equal(t, Cheque{ + id: id, + amount: amount, + user: user, + lock: lock, + }, ev) + }) +} diff --git a/pkg/morph/event/neofs/deposit.go b/pkg/morph/event/neofs/deposit.go new file mode 100644 index 000000000..36dae62e0 --- /dev/null +++ b/pkg/morph/event/neofs/deposit.go @@ -0,0 +1,77 @@ +package neofs + +import ( + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/pkg/errors" +) + +// Deposit structure of neofs.Deposit notification from mainnet chain. +type Deposit struct { + id []byte + amount int64 // Fixed8 + from util.Uint160 + to util.Uint160 +} + +// MorphEvent implements Neo:Morph Event interface. +func (Deposit) MorphEvent() {} + +// ID is a deposit transaction hash. +func (d Deposit) ID() []byte { return d.id } + +// From is a script hash of asset sender in main net. +func (d Deposit) From() util.Uint160 { return d.from } + +// To is a script hash of asset receiver in balance contract. +func (d Deposit) To() util.Uint160 { return d.to } + +// Amount of transferred assets. +func (d Deposit) Amount() int64 { return d.amount } + +// ParseDeposit notification into deposit structure. +func ParseDeposit(params []smartcontract.Parameter) (event.Event, error) { + var ev Deposit + + if ln := len(params); ln != 4 { + return nil, event.WrongNumberOfParameters(4, ln) + } + + // parse from + from, err := client.BytesFromStackParameter(params[0]) + if err != nil { + return nil, errors.Wrap(err, "could not get deposit sender") + } + + ev.from, err = util.Uint160DecodeBytesBE(from) + if err != nil { + return nil, errors.Wrap(err, "could not convert deposit sender to uint160") + } + + // parse amount + ev.amount, err = client.IntFromStackParameter(params[1]) + if err != nil { + return nil, errors.Wrap(err, "could not get deposit amount") + } + + // parse to + to, err := client.BytesFromStackParameter(params[2]) + if err != nil { + return nil, errors.Wrap(err, "could not get deposit receiver") + } + + ev.to, err = util.Uint160DecodeBytesBE(to) + if err != nil { + return nil, errors.Wrap(err, "could not convert deposit receiver to uint160") + } + + // parse id + ev.id, err = client.BytesFromStackParameter(params[3]) + if err != nil { + return nil, errors.Wrap(err, "could not get deposit id") + } + + return ev, nil +} diff --git a/pkg/morph/event/neofs/deposit_test.go b/pkg/morph/event/neofs/deposit_test.go new file mode 100644 index 000000000..4c9e4e087 --- /dev/null +++ b/pkg/morph/event/neofs/deposit_test.go @@ -0,0 +1,123 @@ +package neofs + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/stretchr/testify/require" +) + +func TestParseDeposit(t *testing.T) { + var ( + id = []byte("Hello World") + from = util.Uint160{0x1, 0x2, 0x3} + to = util.Uint160{0x3, 0x2, 0x1} + + amount int64 = 10 + ) + + t.Run("wrong number of parameters", func(t *testing.T) { + prms := []smartcontract.Parameter{ + {}, + {}, + } + + _, err := ParseDeposit(prms) + require.EqualError(t, err, event.WrongNumberOfParameters(4, len(prms)).Error()) + }) + + t.Run("wrong from parameter", func(t *testing.T) { + _, err := ParseDeposit([]smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong amount parameter", func(t *testing.T) { + _, err := ParseDeposit([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: from.BytesBE(), + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong to parameter", func(t *testing.T) { + _, err := ParseDeposit([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: from.BytesBE(), + }, + { + Type: smartcontract.IntegerType, + Value: amount, + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong id parameter", func(t *testing.T) { + _, err := ParseDeposit([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: from.BytesBE(), + }, + { + Type: smartcontract.IntegerType, + Value: amount, + }, + { + Type: smartcontract.ByteArrayType, + Value: to.BytesBE(), + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("correct behavior", func(t *testing.T) { + ev, err := ParseDeposit([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: from.BytesBE(), + }, + { + Type: smartcontract.IntegerType, + Value: amount, + }, + { + Type: smartcontract.ByteArrayType, + Value: to.BytesBE(), + }, + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + }) + + require.NoError(t, err) + require.Equal(t, Deposit{ + id: id, + amount: amount, + from: from, + to: to, + }, ev) + }) +} diff --git a/pkg/morph/event/neofs/withdraw.go b/pkg/morph/event/neofs/withdraw.go new file mode 100644 index 000000000..f8718b710 --- /dev/null +++ b/pkg/morph/event/neofs/withdraw.go @@ -0,0 +1,62 @@ +package neofs + +import ( + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/pkg/errors" +) + +// Withdraw structure of neofs.Withdraw notification from mainnet chain. +type Withdraw struct { + id []byte + amount int64 // Fixed8 + user util.Uint160 +} + +// MorphEvent implements Neo:Morph Event interface. +func (Withdraw) MorphEvent() {} + +// ID is a withdraw transaction hash. +func (w Withdraw) ID() []byte { return w.id } + +// User returns withdraw receiver script hash from main net. +func (w Withdraw) User() util.Uint160 { return w.user } + +// Amount of the withdraw assets. +func (w Withdraw) Amount() int64 { return w.amount } + +// ParseWithdraw notification into withdraw structure. +func ParseWithdraw(params []smartcontract.Parameter) (event.Event, error) { + var ev Withdraw + + if ln := len(params); ln != 3 { + return nil, event.WrongNumberOfParameters(3, ln) + } + + // parse user + user, err := client.BytesFromStackParameter(params[0]) + if err != nil { + return nil, errors.Wrap(err, "could not get withdraw user") + } + + ev.user, err = util.Uint160DecodeBytesBE(user) + if err != nil { + return nil, errors.Wrap(err, "could not convert withdraw user to uint160") + } + + // parse amount + ev.amount, err = client.IntFromStackParameter(params[1]) + if err != nil { + return nil, errors.Wrap(err, "could not get withdraw amount") + } + + // parse id + ev.id, err = client.BytesFromStackParameter(params[2]) + if err != nil { + return nil, errors.Wrap(err, "could not get withdraw id") + } + + return ev, nil +} diff --git a/pkg/morph/event/neofs/withdraw_test.go b/pkg/morph/event/neofs/withdraw_test.go new file mode 100644 index 000000000..2ebccfdb7 --- /dev/null +++ b/pkg/morph/event/neofs/withdraw_test.go @@ -0,0 +1,95 @@ +package neofs + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" + "github.com/stretchr/testify/require" +) + +func TestParseWithdraw(t *testing.T) { + var ( + id = []byte("Hello World") + user = util.Uint160{0x1, 0x2, 0x3} + + amount int64 = 10 + ) + + t.Run("wrong number of parameters", func(t *testing.T) { + prms := []smartcontract.Parameter{ + {}, + {}, + } + + _, err := ParseWithdraw(prms) + require.EqualError(t, err, event.WrongNumberOfParameters(3, len(prms)).Error()) + }) + + t.Run("wrong user parameter", func(t *testing.T) { + _, err := ParseWithdraw([]smartcontract.Parameter{ + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong amount parameter", func(t *testing.T) { + _, err := ParseWithdraw([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("wrong id parameter", func(t *testing.T) { + _, err := ParseWithdraw([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.IntegerType, + Value: amount, + }, + { + Type: smartcontract.ArrayType, + }, + }) + + require.Error(t, err) + }) + + t.Run("correct behavior", func(t *testing.T) { + ev, err := ParseWithdraw([]smartcontract.Parameter{ + { + Type: smartcontract.ByteArrayType, + Value: user.BytesBE(), + }, + { + Type: smartcontract.IntegerType, + Value: amount, + }, + { + Type: smartcontract.ByteArrayType, + Value: id, + }, + }) + + require.NoError(t, err) + require.Equal(t, Withdraw{ + id: id, + amount: amount, + user: user, + }, ev) + }) +} diff --git a/lib/blockchain/event/netmap/epoch.go b/pkg/morph/event/netmap/epoch.go similarity index 81% rename from lib/blockchain/event/netmap/epoch.go rename to pkg/morph/event/netmap/epoch.go index 2445b85a1..09aad6280 100644 --- a/lib/blockchain/event/netmap/epoch.go +++ b/pkg/morph/event/netmap/epoch.go @@ -2,8 +2,8 @@ package netmap import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neofs-node/lib/blockchain/event" - "github.com/nspcc-dev/neofs-node/lib/blockchain/goclient" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" "github.com/pkg/errors" ) @@ -28,7 +28,7 @@ func ParseNewEpoch(prms []smartcontract.Parameter) (event.Event, error) { return nil, event.WrongNumberOfParameters(1, ln) } - prmEpochNum, err := goclient.IntFromStackParameter(prms[0]) + prmEpochNum, err := client.IntFromStackParameter(prms[0]) if err != nil { return nil, errors.Wrap(err, "could not get integer epoch number") } diff --git a/lib/blockchain/event/netmap/epoch_test.go b/pkg/morph/event/netmap/epoch_test.go similarity index 94% rename from lib/blockchain/event/netmap/epoch_test.go rename to pkg/morph/event/netmap/epoch_test.go index 48342697b..72ce8ea97 100644 --- a/lib/blockchain/event/netmap/epoch_test.go +++ b/pkg/morph/event/netmap/epoch_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neofs-node/lib/blockchain/event" + "github.com/nspcc-dev/neofs-node/pkg/morph/event" "github.com/stretchr/testify/require" ) diff --git a/lib/blockchain/event/parser.go b/pkg/morph/event/parser.go similarity index 100% rename from lib/blockchain/event/parser.go rename to pkg/morph/event/parser.go diff --git a/lib/blockchain/event/utils.go b/pkg/morph/event/utils.go similarity index 73% rename from lib/blockchain/event/utils.go rename to pkg/morph/event/utils.go index 66ef187d0..77adcee50 100644 --- a/lib/blockchain/event/utils.go +++ b/pkg/morph/event/utils.go @@ -20,7 +20,8 @@ func (s *scriptHashValue) SetScriptHash(v util.Uint160) { s.hash = v } -func (s scriptHashValue) scriptHash() util.Uint160 { +// ScriptHash is script hash getter. +func (s scriptHashValue) ScriptHash() util.Uint160 { return s.hash } @@ -29,6 +30,7 @@ func (s *typeValue) SetType(v Type) { s.typ = v } -func (s typeValue) getType() Type { +// GetType is an event type getter. +func (s typeValue) GetType() Type { return s.typ } diff --git a/lib/blockchain/subscriber/subscriber.go b/pkg/morph/subscriber/subscriber.go similarity index 98% rename from lib/blockchain/subscriber/subscriber.go rename to pkg/morph/subscriber/subscriber.go index 5d2528e97..c71e7f4d8 100644 --- a/lib/blockchain/subscriber/subscriber.go +++ b/pkg/morph/subscriber/subscriber.go @@ -18,6 +18,7 @@ type ( Subscriber interface { SubscribeForNotification(...util.Uint160) (<-chan *result.NotificationEvent, error) UnsubscribeForNotification() + Close() } subscriber struct { @@ -94,6 +95,10 @@ func (s *subscriber) UnsubscribeForNotification() { } } +func (s *subscriber) Close() { + s.client.Close() +} + func (s *subscriber) routeNotifications(ctx context.Context) { for { select { diff --git a/pkg/network/address.go b/pkg/network/address.go new file mode 100644 index 000000000..5cc4e71c1 --- /dev/null +++ b/pkg/network/address.go @@ -0,0 +1,12 @@ +package network + +import ( + "github.com/multiformats/go-multiaddr" +) + +// Address represents the NeoFS node +// network address. +// +// It is a type alias of +// github.com/multiformats/go-multiaddr.Multiaddr. +type Address = multiaddr.Multiaddr diff --git a/pkg/network/bootstrap/bootstrap.go b/pkg/network/bootstrap/bootstrap.go new file mode 100644 index 000000000..041041dfa --- /dev/null +++ b/pkg/network/bootstrap/bootstrap.go @@ -0,0 +1,60 @@ +package bootstrap + +import ( + "context" + + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" + "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper" +) + +// ContractClient represents the Netmap +// contract client. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper.Wrapper. +type ContractClient = *wrapper.Wrapper + +// NodeInfo represents the +// information about storage node. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/netmap.Info. +type NodeInfo = netmap.Info + +// Registerer represents the tool that +// registers storage node in NeoFS system. +// +// Working Registerer must be created via constructor New. +// Using the Registerer that has been created with new(Registerer) +// expression (or just declaring a Registerer variable) is unsafe +// and can lead to panic. +type Registerer struct { + client ContractClient + + info NodeInfo +} + +// New creates, initializes and returns the Registerer instance. +// +// If passed contract client is nil, wrapper.ErrNilWrapper is returned. +func New(client ContractClient, info NodeInfo) (*Registerer, error) { + if client == nil { + return nil, wrapper.ErrNilWrapper + } + + return &Registerer{ + client: client, + info: info, + }, nil +} + +// Bootstrap registers storage node in NeoFS system +// through Netmap contract client. +// +// If contract client returns error, panic arises without retry. +func (r *Registerer) Bootstrap(context.Context) { + // register peer in NeoFS network + if err := r.client.AddPeer(r.info); err != nil { + panic(err) + } +} diff --git a/pkg/network/dial.go b/pkg/network/dial.go new file mode 100644 index 000000000..a506aeab3 --- /dev/null +++ b/pkg/network/dial.go @@ -0,0 +1,26 @@ +package network + +import ( + "context" + "net" + "time" + + manet "github.com/multiformats/go-multiaddr-net" +) + +// Dial connects to a remote node by address. +func Dial(ctx context.Context, addr Address) (net.Conn, error) { + return dialContext(ctx, addr, 0) +} + +// DialWithTimeout connects to a remote node by address with timeout. +func DialWithTimeout(ctx context.Context, addr Address, timeout time.Duration) (net.Conn, error) { + return dialContext(ctx, addr, timeout) +} + +func dialContext(ctx context.Context, addr Address, timeout time.Duration) (net.Conn, error) { + dialer := manet.Dialer{} + dialer.Timeout = timeout + + return dialer.DialContext(ctx, addr) +} diff --git a/pkg/network/listen.go b/pkg/network/listen.go new file mode 100644 index 000000000..14cd11224 --- /dev/null +++ b/pkg/network/listen.go @@ -0,0 +1,17 @@ +package network + +import ( + "net" + + manet "github.com/multiformats/go-multiaddr-net" +) + +// Listen announces on the local network address. +func Listen(addr Address) (net.Listener, error) { + mLis, err := manet.Listen(addr) + if err != nil { + return nil, err + } + + return manet.NetListener(mLis), nil +} diff --git a/lib/muxer/listener.go b/pkg/network/muxer/listener.go similarity index 100% rename from lib/muxer/listener.go rename to pkg/network/muxer/listener.go diff --git a/lib/muxer/muxer.go b/pkg/network/muxer/muxer.go similarity index 92% rename from lib/muxer/muxer.go rename to pkg/network/muxer/muxer.go index 9aff7cbb0..3f8640382 100644 --- a/lib/muxer/muxer.go +++ b/pkg/network/muxer/muxer.go @@ -8,7 +8,7 @@ import ( "time" "github.com/multiformats/go-multiaddr" - "github.com/nspcc-dev/neofs-node/lib/peers" + "github.com/nspcc-dev/neofs-node/pkg/network" "github.com/soheilhy/cmux" "github.com/valyala/fasthttp" "go.uber.org/zap" @@ -23,7 +23,6 @@ type ( Address multiaddr.Multiaddr ShutdownTTL time.Duration P2P *grpc.Server - Peers peers.Interface } // Mux is an interface of network connections muxer. @@ -33,7 +32,6 @@ type ( } muxer struct { - peers peers.Interface maddr multiaddr.Multiaddr run *int32 lis net.Listener @@ -71,8 +69,8 @@ func New(p Params) Mux { api: p.API, p2p: p.P2P, log: p.Logger, - peers: p.Peers, - done: make(chan struct{}), + + done: make(chan struct{}), } } @@ -109,17 +107,11 @@ func (m *muxer) Start(ctx context.Context) { } } - lis, err := m.peers.Listen(m.maddr) - if err != nil { + if m.lis, err = network.Listen(m.maddr); err != nil { m.log.Fatal("could not close old listener", zap.Error(err)) } - m.lis = NetListener(lis) - - m.log.Info("create mux-listener", - zap.String("bind-address", m.lis.Addr().String())) - mux := cmux.New(m.lis) mux.HandleError(func(e error) bool { if needCatch(e) { diff --git a/lib/muxer/muxer_test.go b/pkg/network/muxer/muxer_test.go similarity index 91% rename from lib/muxer/muxer_test.go rename to pkg/network/muxer/muxer_test.go index fc728d3c0..79a7e1f32 100644 --- a/lib/muxer/muxer_test.go +++ b/pkg/network/muxer/muxer_test.go @@ -13,9 +13,7 @@ import ( "bou.ke/monkey" "github.com/multiformats/go-multiaddr" - "github.com/nspcc-dev/neofs-node/lib/peers" - "github.com/nspcc-dev/neofs-node/lib/test" - "github.com/nspcc-dev/neofs-node/lib/transport" + manet "github.com/multiformats/go-multiaddr-net" "github.com/pkg/errors" "github.com/soheilhy/cmux" "github.com/spf13/viper" @@ -94,16 +92,6 @@ func testMultiAddr(is *require.Assertions) multiaddr.Multiaddr { return mAddr } -func testPeers(is *require.Assertions, a multiaddr.Multiaddr) peers.Interface { - s, err := peers.New(peers.Params{ - Address: a, - Transport: transport.New(5, time.Second), - Logger: test.NewTestLogger(false), - }) - is.NoError(err) - return s -} - func testLogger() *zap.Logger { encoderCfg := zapcore.EncoderConfig{ MessageKey: "msg", @@ -145,7 +133,6 @@ func TestSuite(t *testing.T) { ShutdownTTL: s, API: testHTTPServer(), P2P: g, - Peers: testPeers(is, a), }) is.NotPanics(func() { @@ -181,8 +168,6 @@ func TestSuite(t *testing.T) { addr, err := multiaddr.NewMultiaddr("/ip4/127.0.0.1/tcp/63090") is.NoError(err) - ps := testPeers(is, addr) - RegisterGreeterServer(g, &service{}) m := New(Params{ @@ -190,15 +175,17 @@ func TestSuite(t *testing.T) { Address: addr, ShutdownTTL: s, P2P: g, - Peers: ps, }) is.NotPanics(func() { m.Start(ctx) }) - con, err := ps.GRPCConnection(ctx, addr, false) - is.NoError(err) + a, err := manet.ToNetAddr(addr) + require.NoError(t, err) + + con, err := grpc.DialContext(ctx, a.String(), grpc.WithInsecure()) + require.NoError(t, err) res, err := NewGreeterClient(con).Hello(ctx, &HelloRequest{Name: "test"}) is.NoError(err) @@ -227,7 +214,6 @@ func TestSuite(t *testing.T) { ShutdownTTL: s, API: testHTTPServer(), P2P: g, - Peers: testPeers(is, a), }) is.NotNil(m) @@ -264,7 +250,6 @@ func TestSuite(t *testing.T) { ShutdownTTL: s, API: testHTTPServer(), P2P: g, - Peers: testPeers(is, a), }) is.NotNil(m) @@ -288,7 +273,6 @@ func TestSuite(t *testing.T) { var ( is = require.New(t) l = testLogger() - a = testMultiAddr(is) err error ) @@ -297,7 +281,6 @@ func TestSuite(t *testing.T) { mux := new(muxer) mux.log = l - mux.peers = testPeers(is, a) mux.run = new(int32) mux.done = make(chan struct{}) mux.maddr, err = multiaddr.NewMultiaddr("/ip4/1.1.1.1/tcp/2") @@ -336,7 +319,6 @@ func TestSuite(t *testing.T) { is = require.New(t) g = grpc.NewServer() l = testLogger() - a = testMultiAddr(is) err error ) @@ -347,7 +329,6 @@ func TestSuite(t *testing.T) { mux.api = testHTTPServer() mux.p2p = g mux.log = l - mux.peers = testPeers(is, a) mux.run = new(int32) mux.done = make(chan struct{}) mux.maddr, err = multiaddr.NewMultiaddr("/ip4/1.1.1.1/tcp/2") diff --git a/lib/muxer/muxer_test.pb.go b/pkg/network/muxer/muxer_test.pb.go similarity index 89% rename from lib/muxer/muxer_test.pb.go rename to pkg/network/muxer/muxer_test.pb.go index f998ce85b..a7a905a37 100644 Binary files a/lib/muxer/muxer_test.pb.go and b/pkg/network/muxer/muxer_test.pb.go differ diff --git a/lib/muxer/muxer_test.proto b/pkg/network/muxer/muxer_test.proto similarity index 78% rename from lib/muxer/muxer_test.proto rename to pkg/network/muxer/muxer_test.proto index b3a723f98..3079865c8 100644 --- a/lib/muxer/muxer_test.proto +++ b/pkg/network/muxer/muxer_test.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -option go_package = "github.com/nspcc-dev/neofs-node/lib/muxer"; +option go_package = "github.com/nspcc-dev/neofs-node/pkg/network/muxer"; package muxer; diff --git a/lib/peers/metrics.go b/pkg/network/peers/metrics.go similarity index 100% rename from lib/peers/metrics.go rename to pkg/network/peers/metrics.go diff --git a/pkg/network/peers/peers.go b/pkg/network/peers/peers.go new file mode 100644 index 000000000..0536bd222 --- /dev/null +++ b/pkg/network/peers/peers.go @@ -0,0 +1,234 @@ +package peers + +import ( + "context" + "net" + "sync" + "time" + + "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" + "github.com/nspcc-dev/neofs-node/pkg/network" + "github.com/pkg/errors" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/status" +) + +type ( + // Interface is an interface of network connections controller. + Interface interface { + Job(context.Context) + GRPCConnector + } + + // GRPCConnector is an interface of gRPC virtual connector. + GRPCConnector interface { + GRPCConnection(ctx context.Context, maddr multiaddr.Multiaddr) (*grpc.ClientConn, error) + } + + // Params groups the parameters of Interface. + Params struct { + Logger *zap.Logger + ConnectionTTL time.Duration + ConnectionIDLE time.Duration + MetricsTimeout time.Duration + KeepAliveTTL time.Duration + KeepAlivePingTTL time.Duration + } + + connItem struct { + sync.RWMutex + conn *grpc.ClientConn + used time.Time + } + + iface struct { + log *zap.Logger + tick time.Duration + idle time.Duration + + keepAlive time.Duration + pingTTL time.Duration + + metricsTimeout time.Duration + + grpc struct { + // globalMutex used by garbage collector and other high + globalMutex *sync.RWMutex + // bookMutex resolves concurrent access to the new connection + bookMutex *sync.RWMutex + // connBook contains connection info + // it's mutex resolves concurrent access to existed connection + connBook map[string]*connItem + } + } +) + +const ( + defaultCloseTimer = 30 * time.Second + defaultConIdleTTL = 30 * time.Second + defaultKeepAliveTTL = 5 * time.Second + defaultMetricsTimeout = 5 * time.Second + defaultKeepAlivePingTTL = 50 * time.Millisecond +) + +var errNilMultiaddr = errors.New("empty multi-address") + +func (s *iface) removeGRPCConnection(addr string) error { + if gCon, ok := s.grpc.connBook[addr]; ok && gCon.conn != nil { + if err := gCon.conn.Close(); err != nil { + state, ok := status.FromError(err) + if !ok { + return err + } + + s.log.Debug("error state", + zap.String("address", addr), + zap.Any("code", state.Code()), + zap.String("state", state.Message()), + zap.Any("details", state.Details())) + } + } + + delete(s.grpc.connBook, addr) + + return nil +} + +func isGRPCClosed(con *grpc.ClientConn) bool { + switch con.GetState() { + case connectivity.Idle, connectivity.Connecting, connectivity.Ready: + return false + default: + // connectivity.TransientFailure, connectivity.Shutdown + return true + } +} + +func convertAddress(maddr multiaddr.Multiaddr) (string, error) { + if maddr == nil { + return "", errNilMultiaddr + } + + addr, err := manet.ToNetAddr(maddr) + if err != nil { + return "", errors.Wrapf(err, "could not convert address `%s`", maddr) + } + + return addr.String(), nil +} + +// GRPCConnection creates gRPC connection over peers connection. +func (s *iface) GRPCConnection(ctx context.Context, maddr multiaddr.Multiaddr) (*grpc.ClientConn, error) { + addr, err := convertAddress(maddr) + if err != nil { + return nil, errors.Wrapf(err, "could not convert `%v`", maddr) + } + + // Get global mutex on read. + // All high level function e.g. peers garbage collector + // or shutdown must use globalMutex.Lock instead + s.grpc.globalMutex.RLock() + + // Get connection item from connection book or create a new one. + // Concurrent map access resolved by bookMutex. + s.grpc.bookMutex.Lock() + + item, ok := s.grpc.connBook[addr] + if !ok { + item = new(connItem) + s.grpc.connBook[addr] = item + } + + s.grpc.bookMutex.Unlock() + + // Now lock connection item. + // This denies concurrent access to the same address, + // but allows concurrent access to a different addresses. + item.Lock() + + if item.conn != nil && !isGRPCClosed(item.conn) { + item.used = time.Now() + + item.Unlock() + s.grpc.globalMutex.RUnlock() + + return item.conn, nil + } + + // Если вышеописанные строки переместить внутрь WithDialer, + // мы получим сломанный коннекшн, но ошибка не будет возвращена, + // поэтому мы сначала проверяем коннекшн и лишь потом возвращаем + // *gRPC.ClientConn + // + // Это будет работать с `grpc.WithBlock()`, см. ниже + conn, err := grpc.DialContext(ctx, maddr.String(), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: s.pingTTL, + Timeout: s.keepAlive, + PermitWithoutStream: true, + }), + // TODO: we must provide grpc.WithInsecure() or set credentials + grpc.WithInsecure(), + grpc.WithBlock(), + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { + return network.Dial(ctx, maddr) + }), + ) + if err == nil { + item.conn = conn + item.used = time.Now() + } + + item.Unlock() + s.grpc.globalMutex.RUnlock() + + return conn, err +} + +// New create iface instance and check arguments. +func New(p Params) (Interface, error) { + if p.ConnectionTTL <= 0 { + p.ConnectionTTL = defaultCloseTimer + } + + if p.ConnectionIDLE <= 0 { + p.ConnectionIDLE = defaultConIdleTTL + } + + if p.KeepAliveTTL <= 0 { + p.KeepAliveTTL = defaultKeepAliveTTL + } + + if p.KeepAlivePingTTL <= 0 { + p.KeepAlivePingTTL = defaultKeepAlivePingTTL + } + + if p.MetricsTimeout <= 0 { + p.MetricsTimeout = defaultMetricsTimeout + } + + return &iface{ + tick: p.ConnectionTTL, + idle: p.ConnectionIDLE, + + keepAlive: p.KeepAliveTTL, + pingTTL: p.KeepAlivePingTTL, + + metricsTimeout: p.MetricsTimeout, + + log: p.Logger, + grpc: struct { + globalMutex *sync.RWMutex + bookMutex *sync.RWMutex + connBook map[string]*connItem + }{ + globalMutex: new(sync.RWMutex), + bookMutex: new(sync.RWMutex), + connBook: make(map[string]*connItem), + }, + }, nil +} diff --git a/pkg/network/peers/peers_test.go b/pkg/network/peers/peers_test.go new file mode 100644 index 000000000..fc29228c2 --- /dev/null +++ b/pkg/network/peers/peers_test.go @@ -0,0 +1,211 @@ +package peers + +import ( + "context" + "encoding" + "encoding/json" + "net" + "strings" + "sync" + "testing" + + "github.com/multiformats/go-multiaddr" + "github.com/nspcc-dev/neofs-node/pkg/network" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +type ( + fakeAddress struct { + json.Marshaler + json.Unmarshaler + encoding.TextMarshaler + encoding.TextUnmarshaler + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler + } + + // service is used to implement GreaterServer. + service struct{} +) + +// Hello is simple handler +func (*service) Hello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) { + return &HelloResponse{ + Message: "Hello " + req.Name, + }, nil +} + +var _ multiaddr.Multiaddr = (*fakeAddress)(nil) + +func (fakeAddress) Equal(multiaddr.Multiaddr) bool { + return false +} + +func (fakeAddress) Bytes() []byte { + return nil +} + +func (fakeAddress) String() string { + return "fake" +} + +func (fakeAddress) Protocols() []multiaddr.Protocol { + return []multiaddr.Protocol{{Name: "fake"}} +} + +func (fakeAddress) Encapsulate(multiaddr.Multiaddr) multiaddr.Multiaddr { + panic("implement me") +} + +func (fakeAddress) Decapsulate(multiaddr.Multiaddr) multiaddr.Multiaddr { + panic("implement me") +} + +func (fakeAddress) ValueForProtocol(code int) (string, error) { + return "", nil +} + +const testCount = 10 + +func newTestAddress(t *testing.T) multiaddr.Multiaddr { + lis, err := net.Listen("tcp", "0.0.0.0:0") // nolint:gosec + require.NoError(t, err) + require.NoError(t, lis.Close()) + + l, ok := lis.(*net.TCPListener) + require.True(t, ok) + + _, port, err := net.SplitHostPort(l.Addr().String()) + require.NoError(t, err) + + items := []string{ + "ip4", + "127.0.0.1", + "tcp", + port, + } + + maddr, err := multiaddr.NewMultiaddr("/" + strings.Join(items, "/")) + require.NoError(t, err) + + return maddr +} + +func TestInterface(t *testing.T) { + t.Run("gRPC connection test", func(t *testing.T) { + var ( + err error + s Interface + h = &service{} + g = grpc.NewServer() + a1 = newTestAddress(t) + _ = h + done = make(chan struct{}) + ) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + s, err = New(Params{}) + require.NoError(t, err) + + RegisterGreeterServer(g, h) // register service + + l, err := network.Listen(a1) + require.NoError(t, err) + + defer l.Close() // nolint:golint + + wg := new(sync.WaitGroup) + wg.Add(1) + + go func() { + close(done) + + _ = g.Serve(l) + + wg.Done() + }() + + <-done // wait for server is start listening connections: + + // Fail connection + con, err := s.GRPCConnection(ctx, &fakeAddress{}) + require.Nil(t, con) + require.Error(t, err) + + con, err = s.GRPCConnection(ctx, a1) + require.NoError(t, err) + + cli := NewGreeterClient(con) + resp, err := cli.Hello(ctx, &HelloRequest{ + Name: "Interface test", + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, "Hello Interface test", resp.Message) + + g.GracefulStop() + + wg.Wait() + }) + + t.Run("test grpc connections", func(t *testing.T) { + var ( + ifaces = make([]Interface, 0, testCount) + addresses = make([]multiaddr.Multiaddr, 0, testCount) + ) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + for i := 0; i < testCount; i++ { + addresses = append(addresses, newTestAddress(t)) + + s, err := New(Params{}) + require.NoError(t, err) + + lis, err := network.Listen(addresses[i]) + require.NoError(t, err) + + svc := &service{} + srv := grpc.NewServer() + + RegisterGreeterServer(srv, svc) + + ifaces = append(ifaces, s) + + go func() { + require.NoError(t, srv.Serve(lis)) + }() + } + + const reqName = "test" + wg := new(sync.WaitGroup) + + for i := 0; i < testCount; i++ { + for j := 0; j < testCount; j++ { + wg.Add(1) + go func(i, j int) { + defer wg.Done() + + con, err := ifaces[i].GRPCConnection(ctx, addresses[j]) + require.NoError(t, err) + + cli := NewGreeterClient(con) + + resp, err := cli.Hello(ctx, &HelloRequest{Name: reqName}) + require.NoError(t, err) + + require.Equal(t, "Hello "+reqName, resp.Message) + + require.NoError(t, con.Close()) + }(i, j) + + } + } + + wg.Wait() + }) +} diff --git a/lib/peers/peers_test.pb.go b/pkg/network/peers/peers_test.pb.go similarity index 89% rename from lib/peers/peers_test.pb.go rename to pkg/network/peers/peers_test.pb.go index aa6fe950d..0a26c5a04 100644 Binary files a/lib/peers/peers_test.pb.go and b/pkg/network/peers/peers_test.pb.go differ diff --git a/lib/peers/peers_test.proto b/pkg/network/peers/peers_test.proto similarity index 78% rename from lib/peers/peers_test.proto rename to pkg/network/peers/peers_test.proto index 574409f51..d38ad7654 100644 --- a/lib/peers/peers_test.proto +++ b/pkg/network/peers/peers_test.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -option go_package = "github.com/nspcc-dev/neofs-node/lib/peers"; +option go_package = "github.com/nspcc-dev/neofs-node/pkg/network/peers"; package peers; diff --git a/lib/peers/peerstore.go b/pkg/network/peers/peerstore.go similarity index 99% rename from lib/peers/peerstore.go rename to pkg/network/peers/peerstore.go index 550b5c9db..09b358fa0 100644 --- a/lib/peers/peerstore.go +++ b/pkg/network/peers/peerstore.go @@ -6,7 +6,7 @@ import ( "github.com/multiformats/go-multiaddr" crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/nspcc-dev/neofs-node/lib/netmap" + netmap "github.com/nspcc-dev/neofs-node/pkg/core/netmap" "github.com/pkg/errors" "go.uber.org/zap" ) diff --git a/lib/peers/peerstore_test.go b/pkg/network/peers/peerstore_test.go similarity index 89% rename from lib/peers/peerstore_test.go rename to pkg/network/peers/peerstore_test.go index 2f6449b01..ab0e5ab08 100644 --- a/lib/peers/peerstore_test.go +++ b/pkg/network/peers/peerstore_test.go @@ -6,8 +6,9 @@ import ( "github.com/multiformats/go-multiaddr" crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/nspcc-dev/neofs-node/lib/netmap" - "github.com/nspcc-dev/neofs-node/lib/test" + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" + loggertest "github.com/nspcc-dev/neofs-node/pkg/util/logger/test" + "github.com/nspcc-dev/neofs-node/pkg/util/test" "github.com/stretchr/testify/require" "go.uber.org/zap" ) @@ -39,7 +40,7 @@ func createNetworkMap(t *testing.T) *netmap.NetMap { "Korea": {"Seoul", "Busan"}, "Japan": {"Tokyo", "Kyoto", "Yokohama", "Osaka"}, } - nm = netmap.NewNetmap() + nm = netmap.New() port int64 = 4000 i = 0 ) @@ -52,14 +53,19 @@ func createNetworkMap(t *testing.T) *netmap.NetMap { pk := crypto.MarshalPublicKey(&test.DecodeKey(i).PublicKey) i++ - require.NoError(t, nm.Add(addr, pk, 0, option)) + info := netmap.Info{} + info.SetAddress(addr) + info.SetPublicKey(pk) + info.SetOptions([]string{option}) + + require.NoError(t, nm.AddNode(info)) } } } return nm } -func testMulatiAddress(t *testing.T) multiaddr.Multiaddr { +func testMultiAddress(t *testing.T) multiaddr.Multiaddr { addr, err := multiaddr.NewMultiaddr("/ip4/0.0.0.0/tcp/0") require.NoError(t, err) return addr @@ -67,7 +73,7 @@ func testMulatiAddress(t *testing.T) multiaddr.Multiaddr { func TestPeerstore(t *testing.T) { var ( - l = test.NewTestLogger(debug) + l = loggertest.NewLogger(false) key = test.DecodeKey(1) ) @@ -75,7 +81,7 @@ func TestPeerstore(t *testing.T) { ps, err := NewStore(StoreParams{ Key: key, Logger: l, - Addr: testMulatiAddress(t), + Addr: testMultiAddress(t), }) require.NoError(t, err) require.NotNil(t, ps) @@ -110,7 +116,7 @@ func TestPeerstore(t *testing.T) { ps, err := NewStore(StoreParams{ Key: key, Logger: l, - Addr: testMulatiAddress(t), + Addr: testMultiAddress(t), }) require.NoError(t, err) require.NotNil(t, ps) @@ -118,13 +124,13 @@ func TestPeerstore(t *testing.T) { err = ps.Update(nm) require.NoError(t, err) - expect := nm.Items()[0].PubKey + expect := nm.Nodes()[0].PublicKey() id := IDFromBinary(expect) addr, err := ps.GetAddr(id) require.NoError(t, err) - require.Equal(t, nm.Items()[0].Address, addr.String()) + require.Equal(t, nm.Nodes()[0].Address(), addr.String()) pub, err := ps.GetPublicKey(id) require.NoError(t, err) @@ -200,7 +206,7 @@ func TestPeerstore(t *testing.T) { }) t.Run("Get self address", func(t *testing.T) { - addr := testMulatiAddress(t) + addr := testMultiAddress(t) ps, err := NewStore(StoreParams{ Key: key, @@ -216,7 +222,7 @@ func TestPeerstore(t *testing.T) { }) t.Run("Get ID for multi address", func(t *testing.T) { - addr := testMulatiAddress(t) + addr := testMultiAddress(t) ps, err := NewStore(StoreParams{ Key: key, diff --git a/lib/peers/storage.go b/pkg/network/peers/storage.go similarity index 93% rename from lib/peers/storage.go rename to pkg/network/peers/storage.go index 506464941..80afec628 100644 --- a/lib/peers/storage.go +++ b/pkg/network/peers/storage.go @@ -10,7 +10,7 @@ import ( "github.com/multiformats/go-multihash" "github.com/nspcc-dev/hrw" crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/nspcc-dev/neofs-node/lib/netmap" + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" "github.com/pkg/errors" "github.com/spaolacci/murmur3" "go.uber.org/zap" @@ -248,7 +248,7 @@ func (s *storage) Update(nm *netmap.NetMap) error { s.mu.Lock() defer s.mu.Unlock() - list := nm.ItemsCopy() + list := nm.Nodes() if len(list) == 0 { return errNilNetMap } @@ -256,14 +256,15 @@ func (s *storage) Update(nm *netmap.NetMap) error { items := make(map[ID]Peer, len(s.items)) for i := range list { - addr, err := multiaddr.NewMultiaddr(list[i].Address) + addr, err := multiaddr.NewMultiaddr(list[i].Address()) if err != nil { - return errors.Wrapf(err, "address=`%s`", list[i].Address) + return errors.Wrapf(err, "address=`%s`", list[i].Address()) } - pk := crypto.UnmarshalPublicKey(list[i].PubKey) - if pk == nil && list[i].PubKey != nil { - return errors.Wrapf(errBadPublicKey, "pubkey=`%x`", list[i].PubKey) + pubKey := list[i].PublicKey() + pk := crypto.UnmarshalPublicKey(pubKey) + if pk == nil && pubKey != nil { + return errors.Wrapf(errBadPublicKey, "pubkey=`%x`", pubKey) } id := IDFromPublicKey(pk) diff --git a/lib/peers/worker.go b/pkg/network/peers/worker.go similarity index 100% rename from lib/peers/worker.go rename to pkg/network/peers/worker.go diff --git a/services/public/accounting/service.go b/pkg/network/transport/accounting/grpc/service.go similarity index 64% rename from services/public/accounting/service.go rename to pkg/network/transport/accounting/grpc/service.go index e6baee30b..8497273c6 100644 --- a/services/public/accounting/service.go +++ b/pkg/network/transport/accounting/grpc/service.go @@ -5,9 +5,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/accounting" "github.com/nspcc-dev/neofs-api-go/decimal" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/modules/grpc" + "github.com/nspcc-dev/neofs-node/pkg/morph/client/balance/wrapper" + "github.com/nspcc-dev/neofs-node/pkg/network/transport/grpc" + libgrpc "github.com/nspcc-dev/neofs-node/pkg/network/transport/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -19,22 +19,35 @@ type ( accounting.AccountingServer } + // ContractClient represents the client of Balance contract. + // + // It is a type alias of the pointer to + // github.com/nspcc-dev/neofs-node/pkg/morph/client/balance/wrapper.Wrapper. + ContractClient = *wrapper.Wrapper + // Params groups the parameters of Accounting service server's constructor. Params struct { - MorphBalanceContract implementations.MorphBalanceContract + ContractClient ContractClient } accService struct { - balanceContract implementations.MorphBalanceContract + contractClient ContractClient } ) -var requestVerifyFunc = core.VerifyRequestWithSignatures +var requestVerifyFunc = libgrpc.VerifyRequestWithSignatures // New is an Accounting service server's constructor. +// +// If Balance contract client is nil, +// wrapper.ErrNilWrapper is returned. func New(p Params) (Service, error) { + if p.ContractClient == nil { + return nil, wrapper.ErrNilWrapper + } + return &accService{ - balanceContract: p.MorphBalanceContract, + contractClient: p.ContractClient, }, nil } @@ -48,17 +61,13 @@ func (s accService) Balance(ctx context.Context, req *accounting.BalanceRequest) return nil, status.Error(codes.InvalidArgument, err.Error()) } - // prepare balanceOf parameters - p := implementations.BalanceOfParams{} - p.SetOwnerID(req.GetOwnerID()) - - // get balance of - bRes, err := s.balanceContract.BalanceOf(p) + // get the amount of funds in client's account + fundsAmount, err := s.contractClient.BalanceOf(req.GetOwnerID()) if err != nil { return nil, status.Error(codes.Aborted, err.Error()) } - // get decimals + // get decimals precision of currency transactions // TODO: Reconsider the approach of getting decimals. // @@ -70,15 +79,15 @@ func (s accService) Balance(ctx context.Context, req *accounting.BalanceRequest) // a balance with decimals through a single call. Variations: // - add decimal value stack parameter of balanceOf method; // - create a new method entitled smth like balanceWithDecimals. - decRes, err := s.balanceContract.Decimals(implementations.DecimalsParams{}) + decimals, err := s.contractClient.Decimals() if err != nil { return nil, status.Error(codes.Aborted, err.Error()) } res := new(accounting.BalanceResponse) res.Balance = decimal.NewWithPrecision( - bRes.Amount(), - uint32(decRes.Decimals()), + fundsAmount, + decimals, ) return res, nil diff --git a/services/public/accounting/service_test.go b/pkg/network/transport/accounting/grpc/service_test.go similarity index 100% rename from services/public/accounting/service_test.go rename to pkg/network/transport/accounting/grpc/service_test.go diff --git a/services/public/container/acl.go b/pkg/network/transport/container/grpc/acl.go similarity index 58% rename from services/public/container/acl.go rename to pkg/network/transport/container/grpc/acl.go index 3b96e5a6b..fe942d963 100644 --- a/services/public/container/acl.go +++ b/pkg/network/transport/container/grpc/acl.go @@ -3,8 +3,9 @@ package container import ( "context" + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" "github.com/nspcc-dev/neofs-api-go/container" - "github.com/nspcc-dev/neofs-node/lib/acl" + "github.com/pkg/errors" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -20,16 +21,21 @@ func (s cnrService) SetExtendedACL(ctx context.Context, req *container.SetExtend return nil, status.Error(codes.InvalidArgument, err.Error()) } - // store binary EACL - key := acl.BinaryEACLKey{} - key.SetCID(req.GetID()) + // unmarshal eACL table + table, err := eacl.UnmarshalTable(req.GetEACL()) + if err != nil { + return nil, status.Error( + codes.InvalidArgument, + errors.Wrap(err, "could not decode eACL table").Error(), + ) + } - val := acl.BinaryEACLValue{} - val.SetEACL(req.GetEACL()) - val.SetSignature(req.GetSignature()) - - if err := s.aclStore.PutBinaryEACL(ctx, key, val); err != nil { - return nil, status.Error(codes.Aborted, err.Error()) + // store eACL table + if err := s.aclStore.PutEACL(req.GetID(), table, req.GetSignature()); err != nil { + return nil, status.Error( + codes.Aborted, + errors.Wrap(err, "could not save eACL in storage").Error(), + ) } return new(container.SetExtendedACLResponse), nil @@ -46,19 +52,19 @@ func (s cnrService) GetExtendedACL(ctx context.Context, req *container.GetExtend return nil, status.Error(codes.InvalidArgument, err.Error()) } - // receive binary EACL - key := acl.BinaryEACLKey{} - key.SetCID(req.GetID()) - - val, err := s.aclStore.GetBinaryEACL(ctx, key) + // receive binary eACL + table, err := s.aclStore.GetEACL(req.GetID()) if err != nil { - return nil, status.Error(codes.NotFound, err.Error()) + return nil, status.Error( + codes.NotFound, + errors.Wrap(err, "could not get eACL from storage").Error(), + ) } // fill the response res := new(container.GetExtendedACLResponse) - res.SetEACL(val.EACL()) - res.SetSignature(val.Signature()) + res.SetEACL(eacl.MarshalTable(table)) + res.SetSignature(nil) // TODO: set signature when will appear. return res, nil } diff --git a/services/public/container/acl_test.go b/pkg/network/transport/container/grpc/acl_test.go similarity index 77% rename from services/public/container/acl_test.go rename to pkg/network/transport/container/grpc/acl_test.go index 7c7621d6e..16445b4ba 100644 --- a/services/public/container/acl_test.go +++ b/pkg/network/transport/container/grpc/acl_test.go @@ -5,10 +5,11 @@ import ( "errors" "testing" + "github.com/nspcc-dev/neofs-api-go/acl" + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" "github.com/nspcc-dev/neofs-api-go/container" "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/lib/acl" - "github.com/nspcc-dev/neofs-node/lib/test" + "github.com/nspcc-dev/neofs-node/pkg/util/test" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -30,21 +31,21 @@ type testEACLEntity struct { var requestSignFunc = service.SignRequestData -func (s testEACLEntity) GetBinaryEACL(_ context.Context, key acl.BinaryEACLKey) (acl.BinaryEACLValue, error) { +func (s *testEACLEntity) GetEACL(cid CID) (Table, error) { if s.f != nil { - s.f(key) + s.f(cid) } if s.err != nil { - return acl.BinaryEACLValue{}, s.err + return nil, s.err } - return s.res.(acl.BinaryEACLValue), nil + return s.res.(Table), nil } -func (s testEACLEntity) PutBinaryEACL(_ context.Context, key acl.BinaryEACLKey, val acl.BinaryEACLValue) error { +func (s *testEACLEntity) PutEACL(cid CID, table Table, sig []byte) error { if s.f != nil { - s.f(key, val) + s.f(cid, table, sig) } return s.err @@ -81,11 +82,17 @@ func TestCnrService_SetExtendedACL(t *testing.T) { require.Equal(t, codes.InvalidArgument, st.Code()) }) - t.Run("binary EACL storage failure", func(t *testing.T) { + t.Run("EACL storage failure", func(t *testing.T) { + record := new(acl.EACLRecord) + record.SetAction(acl.EACLRecord_Allow) + + table := eacl.WrapTable(nil) + table.SetRecords([]eacl.Record{eacl.WrapRecord(record)}) + req := new(container.SetExtendedACLRequest) req.SetID(CID{1, 2, 3}) - req.SetEACL([]byte{4, 5, 6}) - req.SetSignature([]byte{7, 8, 9}) + req.SetEACL(eacl.MarshalTable(table)) + req.SetSignature([]byte{4, 5, 6}) require.NoError(t, requestSignFunc(test.DecodeKey(0), req)) @@ -93,12 +100,8 @@ func TestCnrService_SetExtendedACL(t *testing.T) { healthy: new(testCommonEntity), aclStore: &testEACLEntity{ f: func(items ...interface{}) { - key := items[0].(acl.BinaryEACLKey) - require.Equal(t, req.GetID(), key.CID()) - - val := items[1].(acl.BinaryEACLValue) - require.Equal(t, req.GetEACL(), val.EACL()) - require.Equal(t, req.GetSignature(), val.Signature()) + require.Equal(t, req.GetID(), items[0]) + require.Equal(t, req.GetSignature(), items[2]) }, err: errors.New("storage error"), }, @@ -159,7 +162,7 @@ func TestCnrService_GetExtendedACL(t *testing.T) { require.Equal(t, codes.InvalidArgument, st.Code()) }) - t.Run("binary EACL storage failure", func(t *testing.T) { + t.Run("EACL storage failure", func(t *testing.T) { req := new(container.GetExtendedACLRequest) req.SetID(CID{1, 2, 3}) @@ -169,8 +172,7 @@ func TestCnrService_GetExtendedACL(t *testing.T) { healthy: new(testCommonEntity), aclStore: &testEACLEntity{ f: func(items ...interface{}) { - key := items[0].(acl.BinaryEACLKey) - require.Equal(t, req.GetID(), key.CID()) + require.Equal(t, req.GetID(), items[0]) }, err: errors.New("storage error"), }, @@ -186,26 +188,27 @@ func TestCnrService_GetExtendedACL(t *testing.T) { t.Run("correct result", func(t *testing.T) { req := new(container.GetExtendedACLRequest) + req.SetID(CID{1, 2, 3}) require.NoError(t, requestSignFunc(test.DecodeKey(0), req)) - eacl := []byte{1, 2, 3} - sig := []byte{4, 5, 6} + table := eacl.WrapTable(nil) - val := acl.BinaryEACLValue{} - val.SetEACL(eacl) - val.SetSignature(sig) + record := new(acl.EACLRecord) + record.SetAction(acl.EACLRecord_Allow) + + table.SetRecords([]eacl.Record{eacl.WrapRecord(record)}) s := cnrService{ healthy: new(testCommonEntity), aclStore: &testEACLEntity{ - res: val, + res: table, }, } res, err := s.GetExtendedACL(ctx, req) require.NoError(t, err) - require.Equal(t, eacl, res.GetEACL()) - require.Equal(t, sig, res.GetSignature()) + require.Equal(t, eacl.MarshalTable(table), res.GetEACL()) + require.Empty(t, res.GetSignature()) }) } diff --git a/pkg/network/transport/container/grpc/alias.go b/pkg/network/transport/container/grpc/alias.go new file mode 100644 index 000000000..74f9afa33 --- /dev/null +++ b/pkg/network/transport/container/grpc/alias.go @@ -0,0 +1,30 @@ +package container + +import ( + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" + "github.com/nspcc-dev/neofs-node/pkg/core/container" +) + +// CID represents the container identifier. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.ID. +type CID = container.ID + +// OwnerID represents the container owner identifier.. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.OwnerID. +type OwnerID = container.OwnerID + +// Container represents the NeoFS Container structure. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container.Container. +type Container = container.Container + +// Table represents the eACL table. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/acl/extended.ExtendedACLTable. +type Table = eacl.Table diff --git a/services/public/container/common_test.go b/pkg/network/transport/container/grpc/common_test.go similarity index 100% rename from services/public/container/common_test.go rename to pkg/network/transport/container/grpc/common_test.go diff --git a/services/public/container/delete.go b/pkg/network/transport/container/grpc/delete.go similarity index 69% rename from services/public/container/delete.go rename to pkg/network/transport/container/grpc/delete.go index 1df1c88ca..a841242f2 100644 --- a/services/public/container/delete.go +++ b/pkg/network/transport/container/grpc/delete.go @@ -4,7 +4,6 @@ import ( "context" "github.com/nspcc-dev/neofs-api-go/container" - libcnr "github.com/nspcc-dev/neofs-node/lib/container" "github.com/pkg/errors" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -21,15 +20,11 @@ func (s cnrService) Delete(ctx context.Context, req *container.DeleteRequest) (* return nil, status.Error(codes.InvalidArgument, err.Error()) } - p := libcnr.DeleteParams{} - p.SetContext(ctx) - p.SetCID(req.GetCID()) - // TODO: add owner ID and CID signature - - if _, err := s.cnrStore.DeleteContainer(p); err != nil { + // remove container from storage + if err := s.cnrStore.Delete(req.GetCID()); err != nil { return nil, status.Error( codes.Aborted, - errors.Wrapf(err, "could not remove container %d", req.CID).Error(), + errors.Wrap(err, "could not remove container from storage").Error(), ) } diff --git a/services/public/container/delete_test.go b/pkg/network/transport/container/grpc/delete_test.go similarity index 51% rename from services/public/container/delete_test.go rename to pkg/network/transport/container/grpc/delete_test.go index 935e87c70..e4dbefc48 100644 --- a/services/public/container/delete_test.go +++ b/pkg/network/transport/container/grpc/delete_test.go @@ -6,8 +6,6 @@ import ( "testing" "github.com/nspcc-dev/neofs-api-go/container" - libcnr "github.com/nspcc-dev/neofs-node/lib/container" - "github.com/nspcc-dev/neofs-node/lib/test" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -18,7 +16,6 @@ import ( // If err is not nil, it returns as it is. Otherwise, casted to needed type res returns w/o error. type testDeleteEntity struct { // Set of interfaces which entity must implement, but some methods from those does not call. - libcnr.Storage // Argument interceptor. Used for ascertain of correct parameter passage between components. f func(...interface{}) @@ -28,18 +25,6 @@ type testDeleteEntity struct { err error } -func (s testDeleteEntity) DeleteContainer(p libcnr.DeleteParams) (*libcnr.DeleteResult, error) { - if s.f != nil { - s.f(p) - } - - if s.err != nil { - return nil, s.err - } - - return s.res.(*libcnr.DeleteResult), nil -} - func TestCnrService_Delete(t *testing.T) { ctx := context.TODO() @@ -70,49 +55,4 @@ func TestCnrService_Delete(t *testing.T) { require.True(t, ok) require.Equal(t, codes.InvalidArgument, st.Code()) }) - - t.Run("container storage failure", func(t *testing.T) { - req := new(container.DeleteRequest) - req.SetCID(CID{1, 2, 3}) - - require.NoError(t, requestSignFunc(test.DecodeKey(0), req)) - - s := cnrService{ - healthy: new(testCommonEntity), - cnrStore: &testDeleteEntity{ - f: func(items ...interface{}) { - p := items[0].(libcnr.DeleteParams) - require.Equal(t, ctx, p.Context()) - require.Equal(t, req.GetCID(), p.CID()) - }, - err: errors.New("storage error"), - }, - } - - _, err := s.Delete(ctx, req) - require.Error(t, err) - - st, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.Aborted, st.Code()) - }) - - t.Run("correct result", func(t *testing.T) { - req := new(container.DeleteRequest) - - require.NoError(t, requestSignFunc(test.DecodeKey(0), req)) - - delRes := new(libcnr.DeleteResult) - - s := cnrService{ - healthy: new(testCommonEntity), - cnrStore: &testDeleteEntity{ - res: delRes, - }, - } - - res, err := s.Delete(ctx, req) - require.NoError(t, err) - require.NotNil(t, res) - }) } diff --git a/pkg/network/transport/container/grpc/get.go b/pkg/network/transport/container/grpc/get.go new file mode 100644 index 000000000..148018274 --- /dev/null +++ b/pkg/network/transport/container/grpc/get.go @@ -0,0 +1,56 @@ +package container + +import ( + "context" + + "github.com/google/uuid" + "github.com/nspcc-dev/neofs-api-go/container" + "github.com/nspcc-dev/neofs-api-go/refs" + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/basic" + "github.com/pkg/errors" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (s cnrService) Get(ctx context.Context, req *container.GetRequest) (*container.GetResponse, error) { + // check healthiness + if err := s.healthy.Healthy(); err != nil { + return nil, status.Error(codes.Unavailable, err.Error()) + } + + // verify request structure + if err := requestVerifyFunc(req); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + // get container from storage + cnr, err := s.cnrStore.Get(req.GetCID()) + if err != nil { + return nil, status.Error( + codes.NotFound, + errors.Wrap(err, "could not get container from storage").Error(), + ) + } + + // fill the response + res := new(container.GetResponse) + + // FIXME: salt should be []byte in the message + salt, err := uuid.FromBytes(cnr.Salt()) + if err != nil { + return nil, status.Error( + codes.Aborted, + errors.Wrap(err, "could not decode salt").Error(), + ) + } + + // FIXME: message field should be the same type or []byte. + res.Container = new(container.Container) + res.Container.Salt = refs.UUID(salt) + res.Container = new(container.Container) + res.Container.OwnerID = cnr.OwnerID() + res.Container.Rules = cnr.PlacementRule() + res.Container.BasicACL = basic.ToUint32(cnr.BasicACL()) + + return res, nil +} diff --git a/services/public/container/get_test.go b/pkg/network/transport/container/grpc/get_test.go similarity index 50% rename from services/public/container/get_test.go rename to pkg/network/transport/container/grpc/get_test.go index 875a14d74..f3f297841 100644 --- a/services/public/container/get_test.go +++ b/pkg/network/transport/container/grpc/get_test.go @@ -6,8 +6,6 @@ import ( "testing" "github.com/nspcc-dev/neofs-api-go/container" - libcnr "github.com/nspcc-dev/neofs-node/lib/container" - "github.com/nspcc-dev/neofs-node/lib/test" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -18,7 +16,6 @@ import ( // If err is not nil, it returns as it is. Otherwise, casted to needed type res returns w/o error. type testGetEntity struct { // Set of interfaces which entity must implement, but some methods from those does not call. - libcnr.Storage // Argument interceptor. Used for ascertain of correct parameter passage between components. f func(...interface{}) @@ -28,18 +25,6 @@ type testGetEntity struct { err error } -func (s testGetEntity) GetContainer(p libcnr.GetParams) (*libcnr.GetResult, error) { - if s.f != nil { - s.f(p) - } - - if s.err != nil { - return nil, s.err - } - - return s.res.(*libcnr.GetResult), nil -} - func TestCnrService_Get(t *testing.T) { ctx := context.TODO() @@ -70,54 +55,4 @@ func TestCnrService_Get(t *testing.T) { require.True(t, ok) require.Equal(t, codes.InvalidArgument, st.Code()) }) - - t.Run("container storage failure", func(t *testing.T) { - req := new(container.GetRequest) - req.SetCID(CID{1, 2, 3}) - - require.NoError(t, requestSignFunc(test.DecodeKey(0), req)) - - s := cnrService{ - healthy: new(testCommonEntity), - cnrStore: &testGetEntity{ - f: func(items ...interface{}) { - p := items[0].(libcnr.GetParams) - require.Equal(t, ctx, p.Context()) - require.Equal(t, req.GetCID(), p.CID()) - }, - err: errors.New("storage error"), - }, - } - - _, err := s.Get(ctx, req) - require.Error(t, err) - - st, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, st.Code()) - }) - - t.Run("correct result", func(t *testing.T) { - req := new(container.GetRequest) - - require.NoError(t, requestSignFunc(test.DecodeKey(0), req)) - - cnr := &Container{ - Capacity: 1, - } - - getRes := new(libcnr.GetResult) - getRes.SetContainer(cnr) - - s := cnrService{ - healthy: new(testCommonEntity), - cnrStore: &testGetEntity{ - res: getRes, - }, - } - - res, err := s.Get(ctx, req) - require.NoError(t, err) - require.Equal(t, cnr, res.GetContainer()) - }) } diff --git a/services/public/container/list.go b/pkg/network/transport/container/grpc/list.go similarity index 69% rename from services/public/container/list.go rename to pkg/network/transport/container/grpc/list.go index abbe641a8..5ffdcb5f3 100644 --- a/services/public/container/list.go +++ b/pkg/network/transport/container/grpc/list.go @@ -4,7 +4,6 @@ import ( "context" "github.com/nspcc-dev/neofs-api-go/container" - libcnr "github.com/nspcc-dev/neofs-node/lib/container" "github.com/pkg/errors" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -21,19 +20,19 @@ func (s cnrService) List(ctx context.Context, req *container.ListRequest) (*cont return nil, status.Error(codes.InvalidArgument, err.Error()) } - // list containers - p := libcnr.ListParams{} - p.SetContext(ctx) - p.SetOwnerIDList(req.GetOwnerID()) - - lRes, err := s.cnrStore.ListContainers(p) + // list container identifiers from storage + ownerID := req.GetOwnerID() + cidList, err := s.cnrStore.List(&ownerID) if err != nil { - return nil, status.Error(codes.NotFound, err.Error()) + return nil, status.Error( + codes.NotFound, + errors.Wrap(err, "could not list the containers in storage").Error(), + ) } // fill the response res := new(container.ListResponse) - res.CID = lRes.CIDList() + res.CID = cidList return res, nil } diff --git a/pkg/network/transport/container/grpc/list_test.go b/pkg/network/transport/container/grpc/list_test.go new file mode 100644 index 000000000..c8026f152 --- /dev/null +++ b/pkg/network/transport/container/grpc/list_test.go @@ -0,0 +1,58 @@ +package container + +import ( + "context" + "errors" + "testing" + + "github.com/nspcc-dev/neofs-api-go/container" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Entity for mocking interfaces. +// Implementation of any interface intercepts arguments via f (if not nil). +// If err is not nil, it returns as it is. Otherwise, casted to needed type res returns w/o error. +type testListEntity struct { + // Set of interfaces which entity must implement, but some methods from those does not call. + + // Argument interceptor. Used for ascertain of correct parameter passage between components. + f func(...interface{}) + // Mocked result of any interface. + res interface{} + // Mocked error of any interface. + err error +} + +func TestCnrService_List(t *testing.T) { + ctx := context.TODO() + + t.Run("unhealthy", func(t *testing.T) { + s := cnrService{ + healthy: &testCommonEntity{ + err: errors.New("some error"), + }, + } + + _, err := s.List(ctx, new(container.ListRequest)) + require.Error(t, err) + }) + + t.Run("invalid request structure", func(t *testing.T) { + s := cnrService{ + healthy: new(testCommonEntity), + } + + // create unsigned request + req := new(container.ListRequest) + require.Error(t, requestVerifyFunc(req)) + + _, err := s.List(ctx, req) + require.Error(t, err) + + st, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.InvalidArgument, st.Code()) + }) +} diff --git a/services/public/container/put.go b/pkg/network/transport/container/grpc/put.go similarity index 53% rename from services/public/container/put.go rename to pkg/network/transport/container/grpc/put.go index 9ed642ace..a0172f8c9 100644 --- a/services/public/container/put.go +++ b/pkg/network/transport/container/grpc/put.go @@ -5,7 +5,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/container" "github.com/nspcc-dev/neofs-api-go/refs" - libcnr "github.com/nspcc-dev/neofs-node/lib/container" + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/basic" "github.com/pkg/errors" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -24,31 +24,34 @@ func (s cnrService) Put(ctx context.Context, req *container.PutRequest) (*contai } // create container structure - cnr := new(container.Container) - cnr.OwnerID = req.GetOwnerID() - cnr.Capacity = req.GetCapacity() - cnr.Rules = req.GetRules() - cnr.BasicACL = req.GetBasicACL() + // FIXME: message field should be the same type or []byte. + cnr := new(Container) + cnr.SetOwnerID(req.GetOwnerID()) + cnr.SetPlacementRule(req.GetRules()) + cnr.SetBasicACL(basic.FromUint32(req.GetBasicACL())) - var err error - if cnr.Salt, err = refs.NewUUID(); err != nil { - return nil, status.Error(codes.Internal, err.Error()) + uid, err := refs.NewUUID() + if err != nil { + return nil, status.Error( + codes.Aborted, + errors.Wrap(err, "could not generate the salt").Error(), + ) } - // put the container to storage - p := libcnr.PutParams{} - p.SetContext(ctx) - p.SetContainer(cnr) - // TODO: add user signature + cnr.SetSalt(uid.Bytes()) - pRes, err := s.cnrStore.PutContainer(p) + // save container in storage + cid, err := s.cnrStore.Put(cnr) if err != nil { - return nil, status.Error(codes.Aborted, err.Error()) + return nil, status.Error( + codes.Aborted, + errors.Wrap(err, "could not save the container instorage").Error(), + ) } // fill the response res := new(container.PutResponse) - res.CID = pRes.CID() + res.CID = *cid return res, nil } diff --git a/pkg/network/transport/container/grpc/put_test.go b/pkg/network/transport/container/grpc/put_test.go new file mode 100644 index 000000000..7698bec2d --- /dev/null +++ b/pkg/network/transport/container/grpc/put_test.go @@ -0,0 +1,58 @@ +package container + +import ( + "context" + "errors" + "testing" + + "github.com/nspcc-dev/neofs-api-go/container" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Entity for mocking interfaces. +// Implementation of any interface intercepts arguments via f (if not nil). +// If err is not nil, it returns as it is. Otherwise, casted to needed type res returns w/o error. +type testPutEntity struct { + // Set of interfaces which entity must implement, but some methods from those does not call. + + // Argument interceptor. Used for ascertain of correct parameter passage between components. + f func(...interface{}) + // Mocked result of any interface. + res interface{} + // Mocked error of any interface. + err error +} + +func TestCnrService_Put(t *testing.T) { + ctx := context.TODO() + + t.Run("unhealthy", func(t *testing.T) { + s := cnrService{ + healthy: &testCommonEntity{ + err: errors.New("some error"), + }, + } + + _, err := s.Put(ctx, new(container.PutRequest)) + require.Error(t, err) + }) + + t.Run("invalid request structure", func(t *testing.T) { + s := cnrService{ + healthy: new(testCommonEntity), + } + + // create unsigned request + req := new(container.PutRequest) + require.Error(t, requestVerifyFunc(req)) + + _, err := s.Put(ctx, req) + require.Error(t, err) + + st, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, codes.InvalidArgument, st.Code()) + }) +} diff --git a/services/public/container/service.go b/pkg/network/transport/container/grpc/service.go similarity index 61% rename from services/public/container/service.go rename to pkg/network/transport/container/grpc/service.go index 446406c7b..615706fad 100644 --- a/services/public/container/service.go +++ b/pkg/network/transport/container/grpc/service.go @@ -1,12 +1,13 @@ package container import ( + "errors" + "github.com/nspcc-dev/neofs-api-go/container" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/acl" - libcnr "github.com/nspcc-dev/neofs-node/lib/container" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/modules/grpc" + eacl "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + "github.com/nspcc-dev/neofs-node/pkg/network/transport/grpc" + libgrpc "github.com/nspcc-dev/neofs-node/pkg/network/transport/grpc" "go.uber.org/zap" ) @@ -28,9 +29,9 @@ type ( Healthy HealthChecker - Store libcnr.Storage + Store storage.Storage - ExtendedACLStore acl.BinaryExtendedACLStore + ExtendedACLStore eacl.Storage } cnrService struct { @@ -38,19 +39,18 @@ type ( healthy HealthChecker - cnrStore libcnr.Storage + cnrStore storage.Storage - aclStore acl.BinaryExtendedACLStore + aclStore eacl.Storage } ) -const ( - errEmptyLogger = internal.Error("empty log component") - errEmptyStore = internal.Error("empty store component") - errEmptyHealthChecker = internal.Error("empty healthy component") +var ( + errEmptyLogger = errors.New("empty log component") + errEmptyHealthChecker = errors.New("empty healthy component") ) -var requestVerifyFunc = core.VerifyRequestWithSignatures +var requestVerifyFunc = libgrpc.VerifyRequestWithSignatures // New is an Container service server's constructor. func New(p Params) (Service, error) { @@ -58,11 +58,11 @@ func New(p Params) (Service, error) { case p.Logger == nil: return nil, errEmptyLogger case p.Store == nil: - return nil, errEmptyStore + return nil, storage.ErrNilStorage case p.Healthy == nil: return nil, errEmptyHealthChecker case p.ExtendedACLStore == nil: - return nil, acl.ErrNilBinaryExtendedACLStore + return nil, eacl.ErrNilStorage } return &cnrService{ diff --git a/pkg/network/transport/grpc/service.go b/pkg/network/transport/grpc/service.go new file mode 100644 index 000000000..3c8ed9545 --- /dev/null +++ b/pkg/network/transport/grpc/service.go @@ -0,0 +1,13 @@ +package grpc + +import ( + "google.golang.org/grpc" +) + +type Server = grpc.Server + +// Service interface +type Service interface { + Name() string + Register(*Server) +} diff --git a/lib/core/validator.go b/pkg/network/transport/grpc/validate.go similarity index 81% rename from lib/core/validator.go rename to pkg/network/transport/grpc/validate.go index ca66a93a1..41b8b4d6c 100644 --- a/lib/core/validator.go +++ b/pkg/network/transport/grpc/validate.go @@ -1,13 +1,14 @@ -package core +package grpc import ( + "errors" + "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/internal" ) // ErrMissingKeySignPairs is returned by functions that expect // a non-empty SignKeyPair slice, but received empty. -const ErrMissingKeySignPairs = internal.Error("missing key-signature pairs") +var ErrMissingKeySignPairs = errors.New("missing key-signature pairs") // VerifyRequestWithSignatures checks if request has signatures and all of them are valid. // diff --git a/services/metrics/service.go b/pkg/network/transport/metrics/grpc/service.go similarity index 79% rename from services/metrics/service.go rename to pkg/network/transport/metrics/grpc/service.go index 1acd9970e..51ba6e78d 100644 --- a/services/metrics/service.go +++ b/pkg/network/transport/metrics/grpc/service.go @@ -2,10 +2,10 @@ package metrics import ( "context" + "errors" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/metrics" - "github.com/nspcc-dev/neofs-node/modules/grpc" + "github.com/nspcc-dev/neofs-node/pkg/network/transport/grpc" + "github.com/nspcc-dev/neofs-node/pkg/services/metrics" "go.uber.org/zap" ) @@ -28,9 +28,9 @@ type ( } ) -const ( - errEmptyLogger = internal.Error("empty logger") - errEmptyCollector = internal.Error("empty metrics collector") +var ( + errEmptyLogger = errors.New("empty logger") + errEmptyCollector = errors.New("empty metrics collector") ) // New is a Metrics service server's constructor. diff --git a/services/metrics/service.pb.go b/pkg/network/transport/metrics/grpc/service.pb.go similarity index 87% rename from services/metrics/service.pb.go rename to pkg/network/transport/metrics/grpc/service.pb.go index 14062bfba..d9af9ea8b 100644 Binary files a/services/metrics/service.pb.go and b/pkg/network/transport/metrics/grpc/service.pb.go differ diff --git a/services/metrics/service.proto b/pkg/network/transport/metrics/grpc/service.proto similarity index 67% rename from services/metrics/service.proto rename to pkg/network/transport/metrics/grpc/service.proto index 6d4b29f63..fcec48992 100644 --- a/services/metrics/service.proto +++ b/pkg/network/transport/metrics/grpc/service.proto @@ -1,6 +1,6 @@ syntax = "proto3"; package metrics; -option go_package = "github.com/nspcc-dev/neofs-node/service/metrics"; +option go_package = "github.com/nspcc-dev/neofs-node/pkg/network/transport/grpc/metrics"; service Metrics { rpc ResetSpaceCounter(ResetSpaceRequest) returns (ResetSpaceResponse); diff --git a/pkg/network/transport/object/grpc/acl.go b/pkg/network/transport/object/grpc/acl.go new file mode 100644 index 000000000..f40dd32cb --- /dev/null +++ b/pkg/network/transport/object/grpc/acl.go @@ -0,0 +1,742 @@ +package object + +import ( + "bytes" + "context" + "crypto/ecdsa" + "fmt" + "strconv" + + "github.com/multiformats/go-multiaddr" + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" + "github.com/nspcc-dev/neofs-api-go/object" + "github.com/nspcc-dev/neofs-api-go/refs" + "github.com/nspcc-dev/neofs-api-go/service" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-node/pkg/core/container" + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended" + eaclstorage "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + eaclcheck "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc/eacl" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +type ( + // RequestTargeter is an interface of request's ACL group calculator. + RequestTargeter interface { + Target(context.Context, serviceRequest) requestTarget + } + + // aclPreProcessor is an implementation of requestPreProcessor interface. + aclPreProcessor struct { + log *zap.Logger + + aclInfoReceiver aclInfoReceiver + + reqActionCalc requestActionCalculator + + localStore localstore.Localstore + + extACLSource eaclstorage.Storage + + bearerVerifier bearerTokenVerifier + } + + // duplicates NetmapClient method, used for testing. + irKeysReceiver interface { + InnerRingKeys() ([][]byte, error) + } + + containerNodesLister interface { + ContainerNodes(ctx context.Context, cid CID) ([]multiaddr.Multiaddr, error) + ContainerNodesInfo(ctx context.Context, cid CID, prev int) ([]netmap.Info, error) + } + + targetFinder struct { + log *zap.Logger + + irKeysRecv irKeysReceiver + cnrLister containerNodesLister + cnrStorage storage.Storage + } +) + +type objectHeadersSource interface { + getHeaders() (*Object, bool) +} + +type requestActionCalculator interface { + calculateRequestAction(context.Context, requestActionParams) eacl.Action +} + +type aclInfoReceiver struct { + cnrStorage storage.Storage + + targetFinder RequestTargeter +} + +type aclInfo struct { + rule container.BasicACL + + checkExtended bool + + checkBearer bool + + targetInfo requestTarget +} + +type reqActionCalc struct { + storage eaclstorage.Storage + + log *zap.Logger +} + +type serviceRequestInfo struct { + group eacl.Group + + req serviceRequest + + objHdrSrc objectHeadersSource +} + +type requestObjHdrSrc struct { + req serviceRequest + + ls localstore.Localstore +} + +type eaclFromBearer struct { + eaclstorage.Storage + + bearer service.BearerToken +} + +type requestTarget struct { + group eacl.Group + + ir bool +} + +var _ requestPreProcessor = (*aclPreProcessor)(nil) + +var errMissingSignatures = errors.New("empty signature list") + +func (p *aclPreProcessor) preProcess(ctx context.Context, req serviceRequest) error { + if req == nil { + panic(pmEmptyServiceRequest) + } + + // fetch ACL info + aclInfo, err := p.aclInfoReceiver.getACLInfo(ctx, req) + if err != nil { + p.log.Warn("can't get acl of the container", zap.Stringer("cid", req.CID())) + return errAccessDenied + } + + // check basic ACL permissions + var checkFn func(uint8) bool + + switch aclInfo.targetInfo.group { + case eacl.GroupUser: + checkFn = aclInfo.rule.UserAllowed + case eacl.GroupSystem: + checkFn = aclInfo.rule.SystemAllowed + case eacl.GroupOthers: + checkFn = aclInfo.rule.OthersAllowed + default: + panic(fmt.Sprintf("unknown request group (aclPreProcessor): %d", aclInfo.targetInfo.group)) + } + + if requestType := req.Type(); !checkFn(requestACLSection(requestType)) || + aclInfo.targetInfo.ir && !allowedInnerRingRequest(requestType) { + return errAccessDenied + } + + if aclInfo.targetInfo.group != eacl.GroupSystem && + aclInfo.rule.Sticky() && + !checkObjectRequestOwnerMatch(req) { + return errAccessDenied + } + + if !aclInfo.checkBearer && !aclInfo.checkExtended { + return nil + } + + actionParams := requestActionParams{ + eaclSrc: p.extACLSource, + request: req, + objHdrSrc: &requestObjHdrSrc{ + req: req, + ls: p.localStore, + }, + group: aclInfo.targetInfo.group, + } + + if aclInfo.checkBearer { + bearer := req.GetBearerToken() + + if err := p.bearerVerifier.verifyBearerToken(ctx, req.CID(), bearer); err != nil { + p.log.Warn("bearer token verification failure", + zap.String("error", err.Error()), + ) + + return errAccessDenied + } + + actionParams.eaclSrc = eaclFromBearer{ + bearer: bearer, + } + } + + if p.reqActionCalc.calculateRequestAction(ctx, actionParams) != eacl.ActionAllow { + return errAccessDenied + } + + return nil +} + +func (t *targetFinder) Target(ctx context.Context, req serviceRequest) requestTarget { + res := requestTarget{ + group: eacl.GroupUnknown, + } + + ownerID, ownerKey, err := requestOwner(req) + if err != nil { + t.log.Warn("could not get request owner", + zap.String("error", err.Error()), + ) + + return res + } else if ownerKey == nil { + t.log.Warn("signature with nil public key detected") + return res + } + + // if request from container owner then return GroupUser + isOwner, err := isContainerOwner(t.cnrStorage, req.CID(), ownerID) + if err != nil { + t.log.Warn("can't check container owner", zap.String("err", err.Error())) + return res + } else if isOwner { + res.group = eacl.GroupUser + return res + } + + ownerKeyBytes := crypto.MarshalPublicKey(ownerKey) + + // if request from inner ring then return GroupSystem + irKeyList, err := t.irKeysRecv.InnerRingKeys() + if err != nil { + t.log.Warn("could not verify the key belongs to the IR node", zap.String("err", err.Error())) + return res + } + + for i := range irKeyList { + if bytes.Equal(irKeyList[i], ownerKeyBytes) { + res.group = eacl.GroupSystem + res.ir = true + return res + } + } + + // if request from current container node then return GroupSystem + cnr, err := t.cnrLister.ContainerNodesInfo(ctx, req.CID(), 0) + if err != nil { + t.log.Warn("can't get current container list", zap.String("err", err.Error())) + return res + } + + for i := range cnr { + if bytes.Equal(cnr[i].PublicKey(), ownerKeyBytes) { + res.group = eacl.GroupSystem + return res + } + } + + // if request from previous container node then return GroupSystem + cnr, err = t.cnrLister.ContainerNodesInfo(ctx, req.CID(), 1) + if err != nil { + t.log.Warn("can't get previous container list", zap.String("err", err.Error())) + return res + } + + for i := range cnr { + if bytes.Equal(cnr[i].PublicKey(), ownerKeyBytes) { + res.group = eacl.GroupSystem + return res + } + } + + res.group = eacl.GroupOthers + + // if none of the above return GroupOthers + return res +} + +func checkObjectRequestOwnerMatch(req serviceRequest) bool { + rt := req.Type() + + // ignore all request types except Put and Delete + if rt != object.RequestPut && rt != object.RequestDelete { + return true + } + + // get request owner + reqOwner, _, err := requestOwner(req) + if err != nil { + return false + } + + var payloadOwner OwnerID + + // get owner from request payload + if rt == object.RequestPut { + obj := req.(transport.PutInfo).GetHead() + if obj == nil { + return false + } + + payloadOwner = obj.GetSystemHeader().OwnerID + } else { + payloadOwner = req.(*object.DeleteRequest).OwnerID + } + + return reqOwner.Equal(payloadOwner) +} + +// FIXME: this solution only works with healthy key-to-owner conversion. +func requestOwner(req serviceRequest) (OwnerID, *ecdsa.PublicKey, error) { + // if session token exists => return its owner + if token := req.GetSessionToken(); token != nil { + return token.GetOwnerID(), crypto.UnmarshalPublicKey(token.GetOwnerKey()), nil + } + + signKeys := req.GetSignKeyPairs() + if len(signKeys) == 0 { + return OwnerID{}, nil, errMissingSignatures + } + + firstKey := signKeys[0].GetPublicKey() + if firstKey == nil { + return OwnerID{}, nil, crypto.ErrEmptyPublicKey + } + + owner, err := refs.NewOwnerID(firstKey) + + return owner, firstKey, err +} + +// HeadersOfType returns request or object headers. +func (s serviceRequestInfo) HeadersOfType(typ eacl.HeaderType) ([]eacl.Header, bool) { + switch typ { + default: + return nil, true + case eacl.HdrTypeRequest: + return TypedHeaderSourceFromExtendedHeaders(s.req).HeadersOfType(typ) + case eacl.HdrTypeObjSys, eacl.HdrTypeObjUsr: + obj, ok := s.objHdrSrc.getHeaders() + if !ok { + return nil, false + } + + return TypedHeaderSourceFromObject(obj).HeadersOfType(typ) + } +} + +// Key returns a binary representation of sender public key. +func (s serviceRequestInfo) Key() []byte { + _, key, err := requestOwner(s.req) + if err != nil { + return nil + } + + return crypto.MarshalPublicKey(key) +} + +// TypeOf returns true of object request type corresponds to passed OperationType. +func (s serviceRequestInfo) OperationType() eacl.OperationType { + switch t := s.req.Type(); t { + case object.RequestGet: + return eacl.OpTypeGet + case object.RequestPut: + return eacl.OpTypePut + case object.RequestHead: + return eacl.OpTypeHead + case object.RequestSearch: + return eacl.OpTypeSearch + case object.RequestDelete: + return eacl.OpTypeDelete + case object.RequestRange: + return eacl.OpTypeRange + case object.RequestRangeHash: + return eacl.OpTypeRangeHash + default: + panic(fmt.Sprintf("unknown request type (serviceRequestInfo): %d", t)) + } +} + +// Group returns the access group of the request. +func (s serviceRequestInfo) Group() eacl.Group { + return s.group +} + +// CID returns the container identifier of request. +func (s serviceRequestInfo) CID() CID { + return s.req.CID() +} + +func (s requestObjHdrSrc) getHeaders() (*Object, bool) { + switch s.req.Type() { + case object.RequestSearch: + // object header filters is not supported in Search request now + return nil, true + case object.RequestPut: + // for Put we get object headers from request + return s.req.(transport.PutInfo).GetHead(), true + default: + tReq := &transportRequest{ + serviceRequest: s.req, + } + + // for other requests we get object headers from local storage + m, err := s.ls.Meta(tReq.GetAddress()) + if err == nil { + return m.GetObject(), true + } + + return nil, false + } +} + +type requestActionParams struct { + eaclSrc eaclstorage.Storage + + request serviceRequest + + objHdrSrc objectHeadersSource + + group eacl.Group +} + +func (s reqActionCalc) calculateRequestAction(ctx context.Context, p requestActionParams) eacl.Action { + // build eACL validator + validator, err := eaclcheck.NewValidator(p.eaclSrc, s.log) + if err != nil { + s.log.Warn("could not build eacl acl validator", + zap.String("error", err.Error()), + ) + + return eacl.ActionUnknown + } + + // create RequestInfo instance + reqInfo := &serviceRequestInfo{ + group: p.group, + req: p.request, + objHdrSrc: p.objHdrSrc, + } + + // calculate ACL action + return validator.CalculateAction(reqInfo) +} + +func (s aclInfoReceiver) getACLInfo(ctx context.Context, req serviceRequest) (*aclInfo, error) { + cnr, err := s.cnrStorage.Get(req.CID()) + if err != nil { + return nil, err + } + + rule := cnr.BasicACL() + + isBearer := rule.BearerAllowed(requestACLSection(req.Type())) + + // fetch group from the request + t := s.targetFinder.Target(ctx, req) + + return &aclInfo{ + rule: rule, + + checkExtended: !rule.Final(), + + targetInfo: t, + + checkBearer: t.group != eacl.GroupSystem && isBearer && req.GetBearerToken() != nil, + }, nil +} + +func (s eaclFromBearer) GetEACL(cid CID) (eaclstorage.Table, error) { + return eacl.UnmarshalTable(s.bearer.GetACLRules()) +} + +// returns true if request of type argument is allowed for IR needs (audit). +func allowedInnerRingRequest(t object.RequestType) (res bool) { + switch t { + case + object.RequestSearch, + object.RequestHead, + object.RequestRangeHash: + res = true + } + + return +} + +// returns the index number of request section bits. +func requestACLSection(t object.RequestType) uint8 { + switch t { + case object.RequestRangeHash: + return 0 + case object.RequestRange: + return 1 + case object.RequestSearch: + return 2 + case object.RequestDelete: + return 3 + case object.RequestPut: + return 4 + case object.RequestHead: + return 5 + case object.RequestGet: + return 6 + default: + panic(fmt.Sprintf("unknown request type (requestACLSection): %d", t)) + } +} + +type objectHeaderSource struct { + obj *Object +} + +type typedHeader struct { + n string + v string + t eacl.HeaderType +} + +type extendedHeadersWrapper struct { + hdrSrc service.ExtendedHeadersSource +} + +type typedExtendedHeader struct { + hdr service.ExtendedHeader +} + +func newTypedObjSysHdr(name, value string) eacl.TypedHeader { + return &typedHeader{ + n: name, + v: value, + t: eacl.HdrTypeObjSys, + } +} + +// Name is a name field getter. +func (s typedHeader) Name() string { + return s.n +} + +// Value is a value field getter. +func (s typedHeader) Value() string { + return s.v +} + +// HeaderType is a type field getter. +func (s typedHeader) HeaderType() eacl.HeaderType { + return s.t +} + +// TypedHeaderSourceFromObject wraps passed object and returns TypedHeaderSource interface. +func TypedHeaderSourceFromObject(obj *object.Object) extended.TypedHeaderSource { + return &objectHeaderSource{ + obj: obj, + } +} + +// HeaderOfType gathers object headers of passed type and returns Header list. +// +// If value of some header can not be calculated (e.g. nil eacl header), it does not appear in list. +// +// Always returns true. +func (s objectHeaderSource) HeadersOfType(typ eacl.HeaderType) ([]eacl.Header, bool) { + if s.obj == nil { + return nil, true + } + + var res []eacl.Header + + switch typ { + case eacl.HdrTypeObjUsr: + objHeaders := s.obj.GetHeaders() + + res = make([]eacl.Header, 0, len(objHeaders)) // 7 system header fields + + for i := range objHeaders { + if h := newTypedObjectExtendedHeader(objHeaders[i]); h != nil { + res = append(res, h) + } + } + case eacl.HdrTypeObjSys: + res = make([]eacl.Header, 0, 7) + + sysHdr := s.obj.GetSystemHeader() + + created := sysHdr.GetCreatedAt() + + res = append(res, + // ID + newTypedObjSysHdr( + eacl.HdrObjSysNameID, + sysHdr.ID.String(), + ), + + // CID + newTypedObjSysHdr( + eacl.HdrObjSysNameCID, + sysHdr.CID.String(), + ), + + // OwnerID + newTypedObjSysHdr( + eacl.HdrObjSysNameOwnerID, + sysHdr.OwnerID.String(), + ), + + // Version + newTypedObjSysHdr( + eacl.HdrObjSysNameVersion, + strconv.FormatUint(sysHdr.GetVersion(), 10), + ), + + // PayloadLength + newTypedObjSysHdr( + eacl.HdrObjSysNamePayloadLength, + strconv.FormatUint(sysHdr.GetPayloadLength(), 10), + ), + + // CreatedAt.UnitTime + newTypedObjSysHdr( + eacl.HdrObjSysNameCreatedUnix, + strconv.FormatUint(uint64(created.GetUnixTime()), 10), + ), + + // CreatedAt.Epoch + newTypedObjSysHdr( + eacl.HdrObjSysNameCreatedEpoch, + strconv.FormatUint(created.GetEpoch(), 10), + ), + ) + } + + return res, true +} + +func newTypedObjectExtendedHeader(h object.Header) eacl.TypedHeader { + val := h.GetValue() + if val == nil { + return nil + } + + res := new(typedHeader) + res.t = eacl.HdrTypeObjSys + + switch hdr := val.(type) { + case *object.Header_UserHeader: + if hdr.UserHeader == nil { + return nil + } + + res.t = eacl.HdrTypeObjUsr + res.n = hdr.UserHeader.GetKey() + res.v = hdr.UserHeader.GetValue() + case *object.Header_Link: + if hdr.Link == nil { + return nil + } + + switch hdr.Link.GetType() { + case object.Link_Previous: + res.n = eacl.HdrObjSysLinkPrev + case object.Link_Next: + res.n = eacl.HdrObjSysLinkNext + case object.Link_Child: + res.n = eacl.HdrObjSysLinkChild + case object.Link_Parent: + res.n = eacl.HdrObjSysLinkPar + case object.Link_StorageGroup: + res.n = eacl.HdrObjSysLinkSG + default: + return nil + } + + res.v = hdr.Link.ID.String() + default: + return nil + } + + return res +} + +// TypedHeaderSourceFromExtendedHeaders wraps passed ExtendedHeadersSource and returns TypedHeaderSource interface. +func TypedHeaderSourceFromExtendedHeaders(hdrSrc service.ExtendedHeadersSource) extended.TypedHeaderSource { + return &extendedHeadersWrapper{ + hdrSrc: hdrSrc, + } +} + +// Name returns the result of Key method. +func (s typedExtendedHeader) Name() string { + return s.hdr.Key() +} + +// Value returns the result of Value method. +func (s typedExtendedHeader) Value() string { + return s.hdr.Value() +} + +// HeaderType always returns HdrTypeRequest. +func (s typedExtendedHeader) HeaderType() eacl.HeaderType { + return eacl.HdrTypeRequest +} + +// TypedHeaders gathers eacl request headers and returns TypedHeader list. +// +// Nil headers are ignored. +// +// Always returns true. +func (s extendedHeadersWrapper) HeadersOfType(typ eacl.HeaderType) ([]eacl.Header, bool) { + if s.hdrSrc == nil { + return nil, true + } + + var res []eacl.Header + + if typ == eacl.HdrTypeRequest { + hs := s.hdrSrc.ExtendedHeaders() + + res = make([]eacl.Header, 0, len(hs)) + + for i := range hs { + if hs[i] == nil { + continue + } + + res = append(res, &typedExtendedHeader{ + hdr: hs[i], + }) + } + } + + return res, true +} + +func isContainerOwner(storage storage.Storage, cid CID, ownerID OwnerID) (bool, error) { + cnr, err := storage.Get(cid) + if err != nil { + return false, err + } + + return cnr.OwnerID().Equal(ownerID), nil +} diff --git a/services/public/object/acl_test.go b/pkg/network/transport/object/grpc/acl_test.go similarity index 58% rename from services/public/object/acl_test.go rename to pkg/network/transport/object/grpc/acl_test.go index 052791376..ca4ebe7bd 100644 --- a/services/public/object/acl_test.go +++ b/pkg/network/transport/object/grpc/acl_test.go @@ -6,17 +6,18 @@ import ( "errors" "testing" - "github.com/nspcc-dev/neofs-api-go/acl" - "github.com/nspcc-dev/neofs-api-go/bootstrap" + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" "github.com/nspcc-dev/neofs-api-go/container" "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-api-go/service" crypto "github.com/nspcc-dev/neofs-crypto" - libacl "github.com/nspcc-dev/neofs-node/lib/acl" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/ir" - "github.com/nspcc-dev/neofs-node/lib/test" + libcnr "github.com/nspcc-dev/neofs-node/pkg/core/container" + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/basic" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + "github.com/nspcc-dev/neofs-node/pkg/core/netmap" + testlogger "github.com/nspcc-dev/neofs-node/pkg/util/logger/test" + "github.com/nspcc-dev/neofs-node/pkg/util/test" "github.com/stretchr/testify/require" ) @@ -25,11 +26,9 @@ type ( // Set of interfaces which testCommonEntity must implement, but some methods from those does not call. serviceRequest RequestTargeter - implementations.ACLHelper - implementations.ContainerNodesLister - implementations.ContainerOwnerChecker - acl.ExtendedACLTable - libacl.RequestInfo + eacl.Table + storage.Storage + containerNodesLister // Argument interceptor. Used for ascertain of correct parameter passage between components. f func(...interface{}) @@ -41,8 +40,6 @@ type ( ) type testBasicChecker struct { - libacl.BasicChecker - actionErr error action bool @@ -53,68 +50,28 @@ type testBasicChecker struct { bearer bool } -func (t *testACLEntity) calculateRequestAction(context.Context, requestActionParams) acl.ExtendedACLAction { - return t.res.(acl.ExtendedACLAction) -} - -func (t *testACLEntity) buildRequestInfo(req serviceRequest, target acl.Target) (libacl.RequestInfo, error) { - if t.f != nil { - t.f(req, target) - } - +func (t *testACLEntity) Get(cid storage.CID) (*storage.Container, error) { if t.err != nil { return nil, t.err } - return t.res.(libacl.RequestInfo), nil + return t.res.(*storage.Container), nil } -func (t *testACLEntity) Action(table acl.ExtendedACLTable, req libacl.RequestInfo) acl.ExtendedACLAction { - if t.f != nil { - t.f(table, req) - } - - return t.res.(acl.ExtendedACLAction) +func (t *testACLEntity) calculateRequestAction(context.Context, requestActionParams) eacl.Action { + return t.res.(eacl.Action) } -func (t *testACLEntity) GetExtendedACLTable(_ context.Context, cid CID) (acl.ExtendedACLTable, error) { - if t.f != nil { - t.f(cid) - } - - if t.err != nil { - return nil, t.err - } - - return t.res.(acl.ExtendedACLTable), nil -} - -func (s *testBasicChecker) Extended(uint32) bool { - return s.extended -} - -func (s *testBasicChecker) Sticky(uint32) bool { - return s.sticky -} - -func (s *testBasicChecker) Bearer(uint32, object.RequestType) (bool, error) { - return s.bearer, nil -} - -func (s *testBasicChecker) Action(uint32, object.RequestType, acl.Target) (bool, error) { - return s.action, s.actionErr -} - -func (t *testACLEntity) GetBasicACL(context.Context, CID) (uint32, error) { +func (t *testACLEntity) GetBasicACL(context.Context, CID) (libcnr.BasicACL, error) { if t.err != nil { return 0, t.err } - return t.res.(uint32), nil + return t.res.(libcnr.BasicACL), nil } -func (t *testACLEntity) Target(context.Context, serviceRequest) acl.Target { - return t.res.(acl.Target) +func (t *testACLEntity) Target(context.Context, serviceRequest) requestTarget { + return t.res.(requestTarget) } func (t *testACLEntity) CID() CID { return CID{} } @@ -131,34 +88,20 @@ func (t *testACLEntity) GetOwner() (*ecdsa.PublicKey, error) { return t.res.(*ecdsa.PublicKey), nil } -func (t testACLEntity) GetIRInfo(ir.GetInfoParams) (*ir.GetInfoResult, error) { +func (t testACLEntity) InnerRingKeys() ([][]byte, error) { if t.err != nil { return nil, t.err } - res := new(ir.GetInfoResult) - res.SetInfo(*t.res.(*ir.Info)) - - return res, nil + return t.res.([][]byte), nil } -func (t *testACLEntity) ContainerNodesInfo(ctx context.Context, cid CID, prev int) ([]bootstrap.NodeInfo, error) { +func (t *testACLEntity) ContainerNodesInfo(ctx context.Context, cid CID, prev int) ([]netmap.Info, error) { if t.err != nil { return nil, t.err } - return t.res.([][]bootstrap.NodeInfo)[prev], nil -} - -func (t *testACLEntity) IsContainerOwner(_ context.Context, cid CID, owner OwnerID) (bool, error) { - if t.f != nil { - t.f(cid, owner) - } - if t.err != nil { - return false, t.err - } - - return t.res.(bool), nil + return t.res.([][]netmap.Info)[prev], nil } func (t testACLEntity) GetSignKeyPairs() []service.SignKeyPair { @@ -178,35 +121,45 @@ func TestPreprocessor(t *testing.T) { }) t.Run("everything is okay", func(t *testing.T) { - rule := uint32(0x00000003) - // set F-bit - rule |= 1 << 28 + var rule basic.ACL + rule.SetFinal() + rule.AllowOthers(requestACLSection(object.RequestGet)) - checker := new(libacl.BasicACLChecker) + cnr := new(storage.Container) + cnr.SetBasicACL(rule) + + reqTarget := requestTarget{ + group: eacl.GroupOthers, + } preprocessor := aclPreProcessor{ - log: test.NewTestLogger(false), + log: testlogger.NewLogger(false), aclInfoReceiver: aclInfoReceiver{ - basicACLGetter: &testACLEntity{res: rule}, - basicChecker: checker, - targetFinder: &testACLEntity{res: acl.Target_Others}, + cnrStorage: &testACLEntity{ + res: cnr, + }, + targetFinder: &testACLEntity{res: reqTarget}, }, - basicChecker: checker, } require.NoError(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet})) - preprocessor.aclInfoReceiver.targetFinder = &testACLEntity{res: acl.Target_System} - require.Error(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet})) - preprocessor.aclInfoReceiver.targetFinder = &testACLEntity{res: acl.Target_User} + reqTarget.group = eacl.GroupSystem + preprocessor.aclInfoReceiver.targetFinder = &testACLEntity{res: reqTarget} + require.NoError(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet})) + + reqTarget.group = eacl.GroupUser + preprocessor.aclInfoReceiver.targetFinder = &testACLEntity{res: reqTarget} require.Error(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet})) }) t.Run("can't fetch container", func(t *testing.T) { preprocessor := aclPreProcessor{ - log: test.NewTestLogger(false), + log: testlogger.NewLogger(false), aclInfoReceiver: aclInfoReceiver{ - basicACLGetter: &testACLEntity{err: container.ErrNotFound}, - targetFinder: &testACLEntity{res: acl.Target_Others}, + cnrStorage: &testACLEntity{err: container.ErrNotFound}, + targetFinder: &testACLEntity{res: requestTarget{ + group: eacl.GroupOthers, + }}, }, } require.Error(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet})) @@ -214,24 +167,28 @@ func TestPreprocessor(t *testing.T) { }) t.Run("sticky bit", func(t *testing.T) { - checker := &testBasicChecker{ - actionErr: nil, - action: true, - sticky: true, + var rule basic.ACL + rule.SetSticky() + rule.SetFinal() + for i := uint8(0); i < 7; i++ { + rule.AllowUser(i) } + cnr := new(storage.Container) + cnr.SetBasicACL(rule) + s := &aclPreProcessor{ - log: test.NewTestLogger(false), + log: testlogger.NewLogger(false), aclInfoReceiver: aclInfoReceiver{ - basicACLGetter: &testACLEntity{ - res: uint32(0), + cnrStorage: &testACLEntity{ + res: cnr, }, - basicChecker: checker, targetFinder: &testACLEntity{ - res: acl.Target_User, + res: requestTarget{ + group: eacl.GroupUser, + }, }, }, - basicChecker: checker, } ownerKey := &test.DecodeKey(0).PublicKey @@ -354,8 +311,10 @@ func TestPreprocessor(t *testing.T) { } }) - t.Run("extended ACL", func(t *testing.T) { - target := acl.Target_Others + t.Run("eacl ACL", func(t *testing.T) { + target := requestTarget{ + group: eacl.GroupOthers, + } req := &testACLEntity{ res: object.RequestGet, @@ -363,35 +322,78 @@ func TestPreprocessor(t *testing.T) { actCalc := new(testACLEntity) - checker := &testBasicChecker{ - action: true, - extended: true, - } + var rule basic.ACL + rule.AllowOthers(requestACLSection(object.RequestGet)) + + cnr := new(storage.Container) + cnr.SetBasicACL(rule) s := &aclPreProcessor{ - log: test.NewTestLogger(false), + log: testlogger.NewLogger(false), aclInfoReceiver: aclInfoReceiver{ - basicACLGetter: &testACLEntity{ - res: uint32(1), + cnrStorage: &testACLEntity{ + res: cnr, }, - basicChecker: checker, targetFinder: &testACLEntity{ res: target, }, }, - basicChecker: checker, reqActionCalc: actCalc, } // force to return non-ActionAllow - actCalc.res = acl.ActionAllow + 1 + actCalc.res = eacl.ActionAllow + 1 require.EqualError(t, s.preProcess(ctx, req), errAccessDenied.Error()) // force to return ActionAllow - actCalc.res = acl.ActionAllow + actCalc.res = eacl.ActionAllow require.NoError(t, s.preProcess(ctx, req)) }) + + t.Run("inner ring group", func(t *testing.T) { + reqTarget := requestTarget{ + group: eacl.GroupSystem, + ir: true, + } + + cnr := new(storage.Container) + cnr.SetBasicACL(basic.FromUint32(^uint32(0))) + + preprocessor := aclPreProcessor{ + log: testlogger.NewLogger(false), + aclInfoReceiver: aclInfoReceiver{ + cnrStorage: &testACLEntity{res: cnr}, + targetFinder: &testACLEntity{res: reqTarget}, + }, + } + + for _, rt := range []object.RequestType{ + object.RequestSearch, + object.RequestHead, + object.RequestRangeHash, + } { + require.NoError(t, + preprocessor.preProcess(ctx, &testACLEntity{ + res: rt, + }), + ) + } + + for _, rt := range []object.RequestType{ + object.RequestRange, + object.RequestPut, + object.RequestDelete, + object.RequestGet, + } { + require.EqualError(t, + preprocessor.preProcess(ctx, &testACLEntity{ + res: rt, + }), + errAccessDenied.Error(), + ) + } + }) } func TestTargetFinder(t *testing.T) { @@ -400,19 +402,23 @@ func TestTargetFinder(t *testing.T) { containerKey := test.DecodeKey(3) prevContainerKey := test.DecodeKey(4) - irInfo := new(ir.Info) - irNode := ir.Node{} - irNode.SetKey(crypto.MarshalPublicKey(&irKey.PublicKey)) - irInfo.SetNodes([]ir.Node{irNode}) + var infoList1 []netmap.Info + info := netmap.Info{} + info.SetPublicKey(crypto.MarshalPublicKey(&containerKey.PublicKey)) + infoList1 = append(infoList1, info) + + var infoList2 []netmap.Info + info.SetPublicKey(crypto.MarshalPublicKey(&prevContainerKey.PublicKey)) + infoList2 = append(infoList2, info) finder := &targetFinder{ - log: test.NewTestLogger(false), - irStorage: &testACLEntity{ - res: irInfo, + log: testlogger.NewLogger(false), + irKeysRecv: &testACLEntity{ + res: [][]byte{crypto.MarshalPublicKey(&irKey.PublicKey)}, }, - cnrLister: &testACLEntity{res: [][]bootstrap.NodeInfo{ - {{PubKey: crypto.MarshalPublicKey(&containerKey.PublicKey)}}, - {{PubKey: crypto.MarshalPublicKey(&prevContainerKey.PublicKey)}}, + cnrLister: &testACLEntity{res: [][]netmap.Info{ + infoList1, + infoList2, }}, } @@ -434,79 +440,136 @@ func TestTargetFinder(t *testing.T) { req.SetToken(token) req.AddSignKey(nil, pk) - finder.cnrOwnerChecker = &testACLEntity{ - f: func(items ...interface{}) { - require.Equal(t, req.CID(), items[0]) - require.Equal(t, owner, items[1]) - }, - res: true, + cnr := new(storage.Container) + cnr.SetOwnerID(owner) + + finder.cnrStorage = &testACLEntity{ + res: cnr, } - require.Equal(t, acl.Target_User, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupUser, + }, + finder.Target(ctx, req), + ) }) t.Run("container owner", func(t *testing.T) { - finder.cnrOwnerChecker = &testACLEntity{res: true} + key := &test.DecodeKey(0).PublicKey + owner, err := refs.NewOwnerID(key) + require.NoError(t, err) + + cnr := new(storage.Container) + cnr.SetOwnerID(owner) + + finder.cnrStorage = &testACLEntity{res: cnr} req := new(object.SearchRequest) - req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) + req.AddSignKey(nil, key) - require.Equal(t, acl.Target_User, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupUser, + }, + finder.Target(ctx, req), + ) }) t.Run("system owner", func(t *testing.T) { - finder.cnrOwnerChecker = &testACLEntity{res: false} + finder.cnrStorage = &testACLEntity{res: new(storage.Container)} req := new(object.SearchRequest) req.AddSignKey(nil, &irKey.PublicKey) - require.Equal(t, acl.Target_System, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupSystem, + ir: true, + }, + finder.Target(ctx, req), + ) req = new(object.SearchRequest) req.AddSignKey(nil, &containerKey.PublicKey) - require.Equal(t, acl.Target_System, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupSystem, + }, + finder.Target(ctx, req), + ) req = new(object.SearchRequest) req.AddSignKey(nil, &prevContainerKey.PublicKey) - require.Equal(t, acl.Target_System, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupSystem, + }, + finder.Target(ctx, req), + ) }) t.Run("other owner", func(t *testing.T) { - finder.cnrOwnerChecker = &testACLEntity{res: false} + finder.cnrStorage = &testACLEntity{res: new(storage.Container)} req := new(object.SearchRequest) req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) - require.Equal(t, acl.Target_Others, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupOthers, + }, + finder.Target(ctx, req), + ) }) t.Run("can't fetch request owner", func(t *testing.T) { req := new(object.SearchRequest) - require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupUnknown, + }, + finder.Target(ctx, req), + ) }) t.Run("can't fetch container", func(t *testing.T) { - finder.cnrOwnerChecker = &testACLEntity{err: container.ErrNotFound} + finder.cnrStorage = &testACLEntity{err: container.ErrNotFound} req := new(object.SearchRequest) req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) - require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupUnknown, + }, + finder.Target(ctx, req), + ) }) t.Run("can't fetch ir list", func(t *testing.T) { - finder.cnrOwnerChecker = &testACLEntity{res: false} - finder.irStorage = &testACLEntity{err: errors.New("blockchain is busy")} + finder.cnrStorage = &testACLEntity{res: new(storage.Container)} + finder.irKeysRecv = &testACLEntity{err: errors.New("blockchain is busy")} req := new(object.SearchRequest) req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) - require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupUnknown, + }, + finder.Target(ctx, req), + ) }) t.Run("can't fetch container list", func(t *testing.T) { - finder.cnrOwnerChecker = &testACLEntity{res: false} + finder.cnrStorage = &testACLEntity{res: new(storage.Container)} finder.cnrLister = &testACLEntity{err: container.ErrNotFound} req := new(object.SearchRequest) req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) - require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req)) + require.Equal(t, + requestTarget{ + group: eacl.GroupUnknown, + }, + finder.Target(ctx, req), + ) }) } diff --git a/services/public/object/bearer.go b/pkg/network/transport/object/grpc/bearer.go similarity index 89% rename from services/public/object/bearer.go rename to pkg/network/transport/object/grpc/bearer.go index ec01dc584..019d3db67 100644 --- a/services/public/object/bearer.go +++ b/pkg/network/transport/object/grpc/bearer.go @@ -5,7 +5,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/service" crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/nspcc-dev/neofs-node/lib/implementations" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" "github.com/pkg/errors" ) @@ -22,7 +22,7 @@ type bearerActualityVerifier struct { } type bearerOwnershipVerifier struct { - cnrOwnerChecker implementations.ContainerOwnerChecker + cnrStorage storage.Storage } type bearerSignatureVerifier struct{} @@ -54,7 +54,7 @@ func (s bearerActualityVerifier) verifyBearerToken(_ context.Context, _ CID, tok } func (s bearerOwnershipVerifier) verifyBearerToken(ctx context.Context, cid CID, token service.BearerToken) error { - isOwner, err := s.cnrOwnerChecker.IsContainerOwner(ctx, cid, token.GetOwnerID()) + isOwner, err := isContainerOwner(s.cnrStorage, cid, token.GetOwnerID()) if err != nil { return err } else if !isOwner { diff --git a/services/public/object/capacity.go b/pkg/network/transport/object/grpc/capacity.go similarity index 100% rename from services/public/object/capacity.go rename to pkg/network/transport/object/grpc/capacity.go diff --git a/services/public/object/capacity_test.go b/pkg/network/transport/object/grpc/capacity_test.go similarity index 96% rename from services/public/object/capacity_test.go rename to pkg/network/transport/object/grpc/capacity_test.go index deb34afb3..7054f44c1 100644 --- a/services/public/object/capacity_test.go +++ b/pkg/network/transport/object/grpc/capacity_test.go @@ -3,7 +3,7 @@ package object import ( "testing" - "github.com/nspcc-dev/neofs-node/lib/localstore" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" "github.com/stretchr/testify/require" ) diff --git a/services/public/object/delete.go b/pkg/network/transport/object/grpc/delete.go similarity index 93% rename from services/public/object/delete.go rename to pkg/network/transport/object/grpc/delete.go index 8e8c5e2a5..e333d258c 100644 --- a/services/public/object/delete.go +++ b/pkg/network/transport/object/grpc/delete.go @@ -8,9 +8,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/service" "github.com/nspcc-dev/neofs-api-go/session" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/transformer" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport/storagegroup" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -139,8 +139,8 @@ func (s *coreObjRemover) delete(ctx context.Context, dInfo deleteInfo) error { ctx = contextWithValues(ctx, transformer.PrivateSessionToken, pToken, transformer.PublicSessionToken, token, - implementations.BearerToken, dInfo.GetBearerToken(), - implementations.ExtendedHeaders, dInfo.ExtendedHeaders(), + storagegroup.BearerToken, dInfo.GetBearerToken(), + storagegroup.ExtendedHeaders, dInfo.ExtendedHeaders(), ) for i := range deleteList { @@ -172,8 +172,8 @@ func (s *coreDelPreparer) prepare(ctx context.Context, src deleteInfo) ([]delete ctx = contextWithValues(ctx, transformer.PublicSessionToken, src.GetSessionToken(), - implementations.BearerToken, bearer, - implementations.ExtendedHeaders, extHdrs, + storagegroup.BearerToken, bearer, + storagegroup.ExtendedHeaders, extHdrs, ) children := s.childLister.children(ctx, addr) diff --git a/services/public/object/delete_test.go b/pkg/network/transport/object/grpc/delete_test.go similarity index 95% rename from services/public/object/delete_test.go rename to pkg/network/transport/object/grpc/delete_test.go index c954a7c35..af4e8cf72 100644 --- a/services/public/object/delete_test.go +++ b/pkg/network/transport/object/grpc/delete_test.go @@ -8,10 +8,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/service" "github.com/nspcc-dev/neofs-api-go/session" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/rand" - "github.com/nspcc-dev/neofs-node/lib/transformer" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/nspcc-dev/neofs-node/pkg/util/rand" "github.com/pkg/errors" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -121,7 +120,7 @@ func Test_objectService_Delete(t *testing.T) { req := &object.DeleteRequest{Address: testObjectAddress(t)} t.Run("handler error", func(t *testing.T) { - rhErr := internal.Error("test error for request handler") + rhErr := errors.New("test error for request handler") s := &objectService{ statusCalculator: newStatusCalculator(), @@ -182,7 +181,7 @@ func Test_coreObjRemover_delete(t *testing.T) { }) t.Run("prepare error", func(t *testing.T) { - dpErr := internal.Error("test error for delete preparer") + dpErr := errors.New("test error for delete preparer") dp := &testDeleteEntity{ f: func(items ...interface{}) { @@ -205,7 +204,7 @@ func Test_coreObjRemover_delete(t *testing.T) { // ascertain that error returns as expected require.EqualError(t, s.delete(ctx, req), dpErr.Error()) - dp.err = internal.Error("some other error") + dp.err = errors.New("some other error") // ascertain that error returns as expected require.EqualError(t, s.delete(ctx, req), errDeletePrepare.Error()) @@ -220,7 +219,7 @@ func Test_coreObjRemover_delete(t *testing.T) { dInfo, } - srErr := internal.Error("test error for straight remover") + srErr := errors.New("test error for straight remover") s := &coreObjRemover{ delPrep: &testDeleteEntity{ @@ -354,7 +353,7 @@ func Test_straightObjRemover_delete(t *testing.T) { req.setSessionToken(token) t.Run("correct result", func(t *testing.T) { - osErr := internal.Error("test error for object storer") + osErr := errors.New("test error for object storer") s := &straightObjRemover{ tombCreator: &testDeleteEntity{ diff --git a/pkg/network/transport/object/grpc/eacl/validator.go b/pkg/network/transport/object/grpc/eacl/validator.go new file mode 100644 index 000000000..d7ef4eb8a --- /dev/null +++ b/pkg/network/transport/object/grpc/eacl/validator.go @@ -0,0 +1,244 @@ +package eacl + +import ( + "bytes" + + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended" + "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" + "github.com/nspcc-dev/neofs-node/pkg/util/logger" + "go.uber.org/zap" +) + +// RequestInfo represents the information +// about the request. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended.RequestInfo. +type RequestInfo = extended.RequestInfo + +// Action represents action on the request. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended.Action. +type Action = eacl.Action + +// Storage represents the eACL table storage. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage.Storage. +type Storage = storage.Storage + +// Target represents authorization group. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/acl/extended.Target. +type Target = eacl.Target + +// Table represents extended ACL rule table. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/acl/extended.ExtendedACLTable. +type Table = eacl.Table + +// HeaderFilter represents the header filter. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/acl/extended.HeaderFilter. +type HeaderFilter = eacl.HeaderFilter + +// Header represents the string +// key-value header. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended.Header. +type Header = extended.Header + +// MatchType represents value match type. +// +// It is a type alias of +// github.com/nspcc-dev/neofs-api-go/acl/extended.MatchType. +type MatchType = eacl.MatchType + +// Validator is a tool that calculates +// the action on a request according +// to the extended ACL rule table. +// +// Validator receives eACL table from +// the eACL storage. +type Validator struct { + logger *logger.Logger // logging component + + storage Storage // eACL table storage +} + +// NewValidator creates and initializes a new Validator using arguments. +// +// Returns an error if some of the arguments is nil. +// +// Using the Validator that has been created with new(Validator) +// expression (or just declaring a Validator variable) is unsafe +// and can lead to panic. +func NewValidator(st Storage, lg *logger.Logger) (*Validator, error) { + switch { + case st == nil: + return nil, storage.ErrNilStorage + case lg == nil: + return nil, logger.ErrNilLogger + } + + return &Validator{ + logger: lg, + storage: st, + }, nil +} + +// CalculateAction calculates action on the request according +// to its information. +// +// The action is calculated according to the application of +// eACL table of rules to the request. +// +// If request info argument is nil, eacl.ActionUnknown is +// returned immediately. +// +// If the eACL table is not available at the time of the call, +// eacl.ActionUnknown is returned. +// +// If no matching table entry is found, ActionAllow is returned. +func (v *Validator) CalculateAction(info RequestInfo) Action { + if info == nil { + return eacl.ActionUnknown + } + + // get container identifier from request + cid := info.CID() + + // get eACL table by container ID + table, err := v.storage.GetEACL(cid) + if err != nil { + v.logger.Error("could not get eACL table", + zap.Stringer("cid", cid), + zap.String("error", err.Error()), + ) + + return eacl.ActionUnknown + } + + return tableAction(info, table) +} + +// calculates action on the request based on the eACL rules. +func tableAction(info RequestInfo, table Table) Action { + requestOpType := info.OperationType() + + for _, record := range table.Records() { + // check type of operation + if record.OperationType() != requestOpType { + continue + } + + // check target + if !targetMatches(info, record.TargetList()) { + continue + } + + // check headers + switch val := matchFilters(info, record.HeaderFilters()); { + case val < 0: + // headers of some type could not be composed => allow + return eacl.ActionAllow + case val == 0: + return record.Action() + } + } + + return eacl.ActionAllow +} + +// returns: +// - positive value if no matching header is found for at least one filter; +// - zero if at least one suitable header is found for all filters; +// - negative value if the headers of at least one filter cannot be obtained. +func matchFilters(info extended.TypedHeaderSource, filters []HeaderFilter) int { + matched := 0 + + for _, filter := range filters { + // prevent NPE + if filter == nil { + continue + } + + headers, ok := info.HeadersOfType(filter.HeaderType()) + if !ok { + return -1 + } + + // get headers of filtering type + for _, header := range headers { + // prevent NPE + if header == nil { + continue + } + + // check header name + if header.Name() != filter.Name() { + continue + } + + // get match function + matchFn, ok := mMatchFns[filter.MatchType()] + if !ok { + continue + } + + // check match + if !matchFn(header, filter) { + continue + } + + // increment match counter + matched++ + + break + } + } + + return len(filters) - matched +} + +// returns true if one of ExtendedACLTarget has +// suitable target OR suitable public key. +func targetMatches(req RequestInfo, groups []Target) bool { + requestKey := req.Key() + + for _, target := range groups { + recordGroup := target.Group() + + // check public key match + for _, key := range target.KeyList() { + if bytes.Equal(key, requestKey) { + return true + } + } + + // check target group match + if req.Group() == recordGroup { + return true + } + } + + return false +} + +// Maps MatchType to corresponding function. +// 1st argument of function - header value, 2nd - header filter. +var mMatchFns = map[MatchType]func(Header, Header) bool{ + eacl.StringEqual: func(header Header, filter Header) bool { + return header.Value() == filter.Value() + }, + + eacl.StringNotEqual: func(header Header, filter Header) bool { + return header.Value() != filter.Value() + }, +} diff --git a/services/public/object/execution.go b/pkg/network/transport/object/grpc/execution.go similarity index 94% rename from services/public/object/execution.go rename to pkg/network/transport/object/grpc/execution.go index a8880930a..46c5763b7 100644 --- a/services/public/object/execution.go +++ b/pkg/network/transport/object/grpc/execution.go @@ -9,12 +9,11 @@ import ( "github.com/nspcc-dev/neofs-api-go/hash" "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/placement" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/replication/storage" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -59,7 +58,7 @@ type ( prevPlacementBuilder placementBuilder interceptorPreparer interceptorPreparer workerPool WorkerPool - traverseExec implementations.ContainerTraverseExecutor + traverseExec transport.ContainerTraverseExecutor resLogger resultLogger log *zap.Logger } @@ -133,7 +132,7 @@ type ( coreInterceptorPreparer struct { localExec operationExecutor - addressStore implementations.AddressStore + addressStore storage.AddressStore } resultItems struct { @@ -155,11 +154,9 @@ type ( } ) -const ( - errIncompleteOperation = internal.Error("operation is not completed") +const emRangeReadFail = "could not read %d range data" - emRangeReadFail = "could not read %d range data" -) +var errIncompleteOperation = errors.New("operation is not completed") var ( _ resultTracker = (*idleResultTracker)(nil) @@ -276,7 +273,7 @@ func (s *coreOperationFinalizer) completeExecution(ctx context.Context, p operat ctx, cancel := context.WithCancel(ctx) defer cancel() - s.traverseExec.Execute(ctx, implementations.TraverseParams{ + s.traverseExec.Execute(ctx, transport.TraverseParams{ TransportInfo: p.metaInfo, Handler: handler, Traverser: traverser, @@ -446,7 +443,7 @@ func (s *localStoreExecutor) headObject(_ context.Context, addr Address) (*Objec m, err := s.localStore.Meta(addr) if err != nil { switch errors.Cause(err) { - case core.ErrNotFound: + case bucket.ErrNotFound: return nil, errIncompleteOperation default: return nil, err @@ -460,7 +457,7 @@ func (s *localStoreExecutor) getObject(_ context.Context, addr Address) (*Object obj, err := s.localStore.Get(addr) if err != nil { switch errors.Cause(err) { - case core.ErrNotFound: + case bucket.ErrNotFound: return nil, errIncompleteOperation default: return nil, err diff --git a/services/public/object/execution_test.go b/pkg/network/transport/object/grpc/execution_test.go similarity index 90% rename from services/public/object/execution_test.go rename to pkg/network/transport/object/grpc/execution_test.go index 81af16a62..4620c2f23 100644 --- a/services/public/object/execution_test.go +++ b/pkg/network/transport/object/grpc/execution_test.go @@ -11,11 +11,10 @@ import ( "github.com/nspcc-dev/neofs-api-go/hash" "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/replication/storage" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" "github.com/pkg/errors" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -47,26 +46,25 @@ func (s *testExecutionEntity) HandleResult(_ context.Context, n multiaddr.Multia } var ( - _ transport.ResultHandler = (*testExecutionEntity)(nil) - _ interceptorPreparer = (*testExecutionEntity)(nil) - _ implementations.ContainerTraverseExecutor = (*testExecutionEntity)(nil) - _ WorkerPool = (*testExecutionEntity)(nil) - _ operationExecutor = (*testExecutionEntity)(nil) - _ placementBuilder = (*testExecutionEntity)(nil) - _ implementations.AddressStore = (*testExecutionEntity)(nil) - _ executionParamsComputer = (*testExecutionEntity)(nil) - _ operationFinalizer = (*testExecutionEntity)(nil) - _ EpochReceiver = (*testExecutionEntity)(nil) - _ localstore.Localstore = (*testExecutionEntity)(nil) - _ containerTraverser = (*testExecutionEntity)(nil) - _ responseItemHandler = (*testExecutionEntity)(nil) - _ resultTracker = (*testExecutionEntity)(nil) - _ localObjectStorer = (*testExecutionEntity)(nil) - _ localFullObjectReceiver = (*testExecutionEntity)(nil) - _ localHeadReceiver = (*testExecutionEntity)(nil) - _ localQueryImposer = (*testExecutionEntity)(nil) - _ localRangeReader = (*testExecutionEntity)(nil) - _ localRangeHasher = (*testExecutionEntity)(nil) + _ transport.ResultHandler = (*testExecutionEntity)(nil) + _ interceptorPreparer = (*testExecutionEntity)(nil) + _ WorkerPool = (*testExecutionEntity)(nil) + _ operationExecutor = (*testExecutionEntity)(nil) + _ placementBuilder = (*testExecutionEntity)(nil) + _ storage.AddressStore = (*testExecutionEntity)(nil) + _ executionParamsComputer = (*testExecutionEntity)(nil) + _ operationFinalizer = (*testExecutionEntity)(nil) + _ EpochReceiver = (*testExecutionEntity)(nil) + _ localstore.Localstore = (*testExecutionEntity)(nil) + _ containerTraverser = (*testExecutionEntity)(nil) + _ responseItemHandler = (*testExecutionEntity)(nil) + _ resultTracker = (*testExecutionEntity)(nil) + _ localObjectStorer = (*testExecutionEntity)(nil) + _ localFullObjectReceiver = (*testExecutionEntity)(nil) + _ localHeadReceiver = (*testExecutionEntity)(nil) + _ localQueryImposer = (*testExecutionEntity)(nil) + _ localRangeReader = (*testExecutionEntity)(nil) + _ localRangeHasher = (*testExecutionEntity)(nil) ) func (s *testExecutionEntity) prepareInterceptor(p interceptorItems) (func(context.Context, multiaddr.Multiaddr) bool, error) { @@ -79,7 +77,7 @@ func (s *testExecutionEntity) prepareInterceptor(p interceptorItems) (func(conte return s.res.(func(context.Context, multiaddr.Multiaddr) bool), nil } -func (s *testExecutionEntity) Execute(_ context.Context, p implementations.TraverseParams) { +func (s *testExecutionEntity) Execute(_ context.Context, p transport.TraverseParams) { if s.f != nil { s.f(p) } @@ -418,7 +416,7 @@ func Test_coreOperationExecutor_executeOperation(t *testing.T) { p := new(testExecutionEntity) req := newRawPutInfo() req.setTTL(1) - finErr := internal.Error("test error for operation finalizer") + finErr := errors.New("test error for operation finalizer") s := &coreOperationExecutor{ pre: &testExecutionEntity{ @@ -449,7 +447,7 @@ func Test_coreOperationExecutor_executeOperation(t *testing.T) { t.Run("zero ttl", func(t *testing.T) { p := new(testExecutionEntity) req := newRawPutInfo() - finErr := internal.Error("test error for operation finalizer") + finErr := errors.New("test error for operation finalizer") s := &coreOperationExecutor{ loc: &testExecutionEntity{ @@ -476,7 +474,7 @@ func Test_localStoreExecutor(t *testing.T) { t.Run("put", func(t *testing.T) { epoch := uint64(100) obj := new(Object) - putErr := internal.Error("test error for put") + putErr := errors.New("test error for put") ls := &testExecutionEntity{ f: func(items ...interface{}) { @@ -509,7 +507,7 @@ func Test_localStoreExecutor(t *testing.T) { t.Run("get", func(t *testing.T) { t.Run("error", func(t *testing.T) { - getErr := internal.Error("test error for get") + getErr := errors.New("test error for get") ls := &testExecutionEntity{ f: func(items ...interface{}) { @@ -528,7 +526,7 @@ func Test_localStoreExecutor(t *testing.T) { require.EqualError(t, err, getErr.Error()) require.Nil(t, res) - ls.err = errors.Wrap(core.ErrNotFound, "wrap message") + ls.err = errors.Wrap(bucket.ErrNotFound, "wrap message") res, err = s.getObject(ctx, addr) require.EqualError(t, err, errIncompleteOperation.Error()) @@ -552,7 +550,7 @@ func Test_localStoreExecutor(t *testing.T) { t.Run("head", func(t *testing.T) { t.Run("error", func(t *testing.T) { - headErr := internal.Error("test error for head") + headErr := errors.New("test error for head") ls := &testExecutionEntity{ err: headErr, @@ -566,7 +564,7 @@ func Test_localStoreExecutor(t *testing.T) { require.EqualError(t, err, headErr.Error()) require.Nil(t, res) - ls.err = errors.Wrap(core.ErrNotFound, "wrap message") + ls.err = errors.Wrap(bucket.ErrNotFound, "wrap message") res, err = s.headObject(ctx, addr) require.EqualError(t, err, errIncompleteOperation.Error()) @@ -590,7 +588,7 @@ func Test_localStoreExecutor(t *testing.T) { t.Run("get range", func(t *testing.T) { t.Run("error", func(t *testing.T) { - rngErr := internal.Error("test error for range reader") + rngErr := errors.New("test error for range reader") s := &localStoreExecutor{ localStore: &testExecutionEntity{ @@ -638,7 +636,7 @@ func Test_localStoreExecutor(t *testing.T) { }) t.Run("error", func(t *testing.T) { - rhErr := internal.Error("test error for range hasher") + rhErr := errors.New("test error for range hasher") s := &localStoreExecutor{ localStore: &testExecutionEntity{ @@ -692,7 +690,7 @@ func Test_coreHandler_HandleResult(t *testing.T) { t.Run("error", func(t *testing.T) { handled := false - err := internal.Error("") + err := errors.New("") s := &coreHandler{ traverser: &testExecutionEntity{ @@ -776,7 +774,7 @@ func Test_localOperationExecutor_executeOperation(t *testing.T) { }} t.Run("error", func(t *testing.T) { - putErr := internal.Error("test error for put") + putErr := errors.New("test error for put") s := &localOperationExecutor{ objStore: &testExecutionEntity{ @@ -813,7 +811,7 @@ func Test_localOperationExecutor_executeOperation(t *testing.T) { req.setAddress(addr) t.Run("error", func(t *testing.T) { - getErr := internal.Error("test error for get") + getErr := errors.New("test error for get") s := &localOperationExecutor{ objRecv: &testExecutionEntity{ @@ -853,7 +851,7 @@ func Test_localOperationExecutor_executeOperation(t *testing.T) { }} t.Run("error", func(t *testing.T) { - headErr := internal.Error("test error for head") + headErr := errors.New("test error for head") s := &localOperationExecutor{ headRecv: &testExecutionEntity{ @@ -897,7 +895,7 @@ func Test_localOperationExecutor_executeOperation(t *testing.T) { }} t.Run("error", func(t *testing.T) { - searchErr := internal.Error("test error for search") + searchErr := errors.New("test error for search") s := &localOperationExecutor{ queryImp: &testExecutionEntity{ @@ -943,7 +941,7 @@ func Test_localOperationExecutor_executeOperation(t *testing.T) { req.setRange(rng) t.Run("error", func(t *testing.T) { - rrErr := internal.Error("test error for range reader") + rrErr := errors.New("test error for range reader") s := &localOperationExecutor{ rngReader: &testExecutionEntity{ @@ -996,7 +994,7 @@ func Test_localOperationExecutor_executeOperation(t *testing.T) { req.setSalt(salt) t.Run("error", func(t *testing.T) { - rhErr := internal.Error("test error for range hasher") + rhErr := errors.New("test error for range hasher") s := &localOperationExecutor{ rngHasher: &testExecutionEntity{ @@ -1042,7 +1040,7 @@ func Test_coreOperationFinalizer_completeExecution(t *testing.T) { ctx := context.TODO() t.Run("address store failure", func(t *testing.T) { - asErr := internal.Error("test error for address store") + asErr := errors.New("test error for address store") s := &coreOperationFinalizer{ interceptorPreparer: &testExecutionEntity{ @@ -1094,7 +1092,7 @@ func Test_coreOperationFinalizer_completeExecution(t *testing.T) { traverseExec: &testExecutionEntity{ f: func(items ...interface{}) { t.Run("correct traverse executor params", func(t *testing.T) { - p := items[0].(implementations.TraverseParams) + p := items[0].(transport.TraverseParams) require.True(t, p.ExecutionInterceptor(ctx, nil)) require.Equal(t, req, p.TransportInfo) @@ -1123,7 +1121,7 @@ func Test_coreOperationFinalizer_completeExecution(t *testing.T) { func Test_coreInterceptorPreparer_prepareInterceptor(t *testing.T) { t.Run("address store failure", func(t *testing.T) { - asErr := internal.Error("test error for address store") + asErr := errors.New("test error for address store") s := &coreInterceptorPreparer{ addressStore: &testExecutionEntity{ @@ -1144,7 +1142,7 @@ func Test_coreInterceptorPreparer_prepareInterceptor(t *testing.T) { req := new(transportRequest) itemHandler := new(testExecutionEntity) - localErr := internal.Error("test error for local executor") + localErr := errors.New("test error for local executor") p := interceptorItems{ selfForward: true, diff --git a/services/public/object/filter.go b/pkg/network/transport/object/grpc/filter.go similarity index 93% rename from services/public/object/filter.go rename to pkg/network/transport/object/grpc/filter.go index dc8ddc6c9..809b4f588 100644 --- a/services/public/object/filter.go +++ b/pkg/network/transport/object/grpc/filter.go @@ -5,10 +5,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/service" "github.com/nspcc-dev/neofs-api-go/storagegroup" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/objutil" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/verifier" "github.com/pkg/errors" ) @@ -20,7 +19,7 @@ type ( storageCap uint64 localStore localstore.Localstore epochRecv EpochReceiver - verifier objutil.Verifier + verifier verifier.Verifier maxPayloadSize uint64 } @@ -50,13 +49,7 @@ const ( payloadSizeFN = "PAYLOAD_SIZE" ) -const ( - errObjectFilter = internal.Error("incoming object has not passed filter") -) - -var ( - _ tombstonePresenceChecker = (*coreTSPresChecker)(nil) -) +var errObjectFilter = errors.New("incoming object has not passed filter") var mFilters = map[string]filterConstructor{ tombstoneOverwriteFN: tombstoneOverwriteFC, @@ -121,7 +114,7 @@ func newFilter(p *Params, name string, m map[string]filterConstructor) (Filter, func (s *coreTSPresChecker) hasLocalTombstone(addr Address) (bool, error) { m, err := s.localStore.Meta(addr) if err != nil { - if errors.Is(errors.Cause(err), core.ErrNotFound) { + if errors.Is(errors.Cause(err), bucket.ErrNotFound) { return false, nil } diff --git a/services/public/object/filter_test.go b/pkg/network/transport/object/grpc/filter_test.go similarity index 95% rename from services/public/object/filter_test.go rename to pkg/network/transport/object/grpc/filter_test.go index 1b4084f05..e3ce2781f 100644 --- a/services/public/object/filter_test.go +++ b/pkg/network/transport/object/grpc/filter_test.go @@ -8,10 +8,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/service" "github.com/nspcc-dev/neofs-api-go/storagegroup" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/objutil" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/verifier" "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -40,7 +39,7 @@ type ( var ( _ storagegroup.InfoReceiver = (*testFilterEntity)(nil) - _ objutil.Verifier = (*testFilterEntity)(nil) + _ verifier.Verifier = (*testFilterEntity)(nil) _ EpochReceiver = (*testFilterEntity)(nil) _ localstore.Localstore = (*testFilterEntity)(nil) _ tombstonePresenceChecker = (*testFilterEntity)(nil) @@ -161,7 +160,7 @@ func Test_objectIntegrityFC(t *testing.T) { if items[0].(*Object).SystemHeader.ID.Equal(valid.SystemHeader.ID) { ver.err = nil } else { - ver.err = internal.Error("") + ver.err = errors.New("") } } @@ -192,7 +191,7 @@ func Test_tombstoneOverwriteFC(t *testing.T) { ts.f = func(items ...interface{}) { addr := items[0].(Address) if addr.ObjectID.Equal(obj2.SystemHeader.ID) { - ts.res, ts.err = nil, internal.Error("") + ts.res, ts.err = nil, errors.New("") } else if addr.ObjectID.Equal(obj3.SystemHeader.ID) { ts.res, ts.err = true, nil } else { @@ -310,7 +309,7 @@ func Test_coreTSPresChecker(t *testing.T) { f: func(items ...interface{}) { require.Equal(t, addr, items[0]) }, - err: errors.Wrap(core.ErrNotFound, "some message"), + err: errors.Wrap(bucket.ErrNotFound, "some message"), } s := &coreTSPresChecker{localStore: ls} @@ -319,7 +318,7 @@ func Test_coreTSPresChecker(t *testing.T) { require.NoError(t, err) require.False(t, res) - lsErr := internal.Error("test error for local storage") + lsErr := errors.New("test error for local storage") ls.err = lsErr res, err = s.hasLocalTombstone(addr) diff --git a/services/public/object/get.go b/pkg/network/transport/object/grpc/get.go similarity index 100% rename from services/public/object/get.go rename to pkg/network/transport/object/grpc/get.go diff --git a/services/public/object/get_test.go b/pkg/network/transport/object/grpc/get_test.go similarity index 95% rename from services/public/object/get_test.go rename to pkg/network/transport/object/grpc/get_test.go index a78fde76c..1cea57e7e 100644 --- a/services/public/object/get_test.go +++ b/pkg/network/transport/object/grpc/get_test.go @@ -5,8 +5,7 @@ import ( "testing" "github.com/nspcc-dev/neofs-api-go/object" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/localstore" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -106,7 +105,7 @@ func TestObjectService_Get(t *testing.T) { req := &object.GetRequest{Address: testObjectAddress(t)} t.Run("request handler failure", func(t *testing.T) { - hErr := internal.Error("test error for request handler") + hErr := errors.New("test error for request handler") s := &objectService{ statusCalculator: newStatusCalculator(), @@ -127,7 +126,7 @@ func TestObjectService_Get(t *testing.T) { }) t.Run("send object head failure", func(t *testing.T) { - srvErr := internal.Error("test error for get server") + srvErr := errors.New("test error for get server") obj := &Object{ SystemHeader: SystemHeader{ @@ -153,7 +152,7 @@ func TestObjectService_Get(t *testing.T) { }) t.Run("send chunk failure", func(t *testing.T) { - srvErr := internal.Error("test error for get server") + srvErr := errors.New("test error for get server") payload := testData(t, 10) obj := &Object{ diff --git a/services/public/object/handler.go b/pkg/network/transport/object/grpc/handler.go similarity index 100% rename from services/public/object/handler.go rename to pkg/network/transport/object/grpc/handler.go diff --git a/services/public/object/handler_test.go b/pkg/network/transport/object/grpc/handler_test.go similarity index 95% rename from services/public/object/handler_test.go rename to pkg/network/transport/object/grpc/handler_test.go index abc0c7ce0..ce41776b0 100644 --- a/services/public/object/handler_test.go +++ b/pkg/network/transport/object/grpc/handler_test.go @@ -10,8 +10,8 @@ import ( "time" "github.com/nspcc-dev/neofs-api-go/object" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -143,7 +143,7 @@ func TestCoreRequestHandler_HandleRequest(t *testing.T) { t.Run("pre processor error", func(t *testing.T) { // create custom error - pErr := internal.Error("test error for pre-processor") + pErr := errors.New("test error for pre-processor") s := &coreRequestHandler{ preProc: &testHandlerEntity{ @@ -167,7 +167,7 @@ func TestCoreRequestHandler_HandleRequest(t *testing.T) { t.Run("correct behavior", func(t *testing.T) { // create custom error - eErr := internal.Error("test error for request executor") + eErr := errors.New("test error for request executor") // create custom result eRes := testData(t, 10) @@ -250,7 +250,7 @@ func Test_objectService_executeRequest(t *testing.T) { t.Run("put request", func(t *testing.T) { t.Run("storer error", func(t *testing.T) { - sErr := internal.Error("test error for object storer") + sErr := errors.New("test error for object storer") req := &putRequest{ PutRequest: new(object.PutRequest), @@ -277,7 +277,7 @@ func Test_objectService_executeRequest(t *testing.T) { t.Run("correct result", func(t *testing.T) { addr := testObjectAddress(t) - srvErr := internal.Error("test error for stream server") + srvErr := errors.New("test error for stream server") resp := &object.PutResponse{Address: addr} @@ -315,7 +315,7 @@ func Test_objectService_executeRequest(t *testing.T) { t.Run("delete request", func(t *testing.T) { var ( timeout = 3 * time.Second - dErr = internal.Error("test error for object remover") + dErr = errors.New("test error for object remover") req = &object.DeleteRequest{Address: testObjectAddress(t)} ) @@ -365,7 +365,7 @@ func Test_objectService_executeRequest(t *testing.T) { t.Run("head request", func(t *testing.T) { var ( timeout = 3 * time.Second - hErr = internal.Error("test error for head receiver") + hErr = errors.New("test error for head receiver") req = &object.HeadRequest{Address: testObjectAddress(t)} ) @@ -418,7 +418,7 @@ func Test_objectService_executeRequest(t *testing.T) { t.Run("hashes", func(t *testing.T) { var ( timeout = 3 * time.Second - rErr = internal.Error("test error for range receiver") + rErr = errors.New("test error for range receiver") req = &object.GetRangeHashRequest{Address: testObjectAddress(t)} ) diff --git a/services/public/object/head.go b/pkg/network/transport/object/grpc/head.go similarity index 91% rename from services/public/object/head.go rename to pkg/network/transport/object/grpc/head.go index 2eb89ce43..c5aba3269 100644 --- a/services/public/object/head.go +++ b/pkg/network/transport/object/grpc/head.go @@ -9,11 +9,10 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/objio" - "github.com/nspcc-dev/neofs-node/lib/transformer" - "github.com/nspcc-dev/neofs-node/lib/transport" + _range "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc/range" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport/storagegroup" "github.com/panjf2000/ants/v2" "github.com/pkg/errors" "go.uber.org/zap" @@ -117,8 +116,8 @@ type ( } corePayloadRangeReceiver struct { - chopTable objio.ChopperTable - relRecv objio.RelativeReceiver + chopTable _range.ChopperTable + relRecv _range.RelativeReceiver payloadRecv payloadPartReceiver // Set of errors that won't be converted to errPayloadRangeNotFound @@ -141,11 +140,11 @@ type ( emptyReader struct{} ) -const ( - emHeadRecvFail = "could not receive %d of %d object head" +const emHeadRecvFail = "could not receive %d of %d object head" - childrenNotFound = internal.Error("could not find child objects") - errNonAssembly = internal.Error("node is not capable to assemble the object") +var ( + childrenNotFound = errors.New("could not find child objects") + errNonAssembly = errors.New("node is not capable to assemble the object") ) var ( @@ -235,8 +234,8 @@ func (s *coreObjectReceiver) getObject(ctx context.Context, info ...transport.Ge ctx = contextWithValues(ctx, transformer.PublicSessionToken, info[0].GetSessionToken(), - implementations.BearerToken, info[0].GetBearerToken(), - implementations.ExtendedHeaders, info[0].ExtendedHeaders(), + storagegroup.BearerToken, info[0].GetBearerToken(), + storagegroup.ExtendedHeaders, info[0].ExtendedHeaders(), ) if childCount <= 0 { @@ -338,11 +337,11 @@ func (s *corePayloadRangeReceiver) getRangeData(ctx context.Context, info transp addr = info.GetAddress() ) - chopper, err = s.chopTable.GetChopper(addr, objio.RCCharybdis) + chopper, err = s.chopTable.GetChopper(addr, _range.RCCharybdis) if err != nil || !chopper.Closed() { if len(selection) == 0 { - if chopper, err = s.chopTable.GetChopper(addr, objio.RCScylla); err != nil { - if chopper, err = objio.NewScylla(&objio.ChopperParams{ + if chopper, err = s.chopTable.GetChopper(addr, _range.RCScylla); err != nil { + if chopper, err = _range.NewScylla(&_range.ChopperParams{ RelativeReceiver: s.relRecv, Addr: addr, }); err != nil { @@ -361,7 +360,7 @@ func (s *corePayloadRangeReceiver) getRangeData(ctx context.Context, info transp }) } - if chopper, err = objio.NewCharybdis(&objio.CharybdisParams{ + if chopper, err = _range.NewCharybdis(&_range.CharybdisParams{ Addr: addr, ReadySelection: rs, }); err != nil { @@ -376,8 +375,8 @@ func (s *corePayloadRangeReceiver) getRangeData(ctx context.Context, info transp ctx = contextWithValues(ctx, transformer.PublicSessionToken, info.GetSessionToken(), - implementations.BearerToken, info.GetBearerToken(), - implementations.ExtendedHeaders, info.ExtendedHeaders(), + storagegroup.BearerToken, info.GetBearerToken(), + storagegroup.ExtendedHeaders, info.ExtendedHeaders(), ) var rList []RangeDescriptor @@ -534,7 +533,7 @@ func tokenFromContext(ctx context.Context) service.SessionToken { } func bearerFromContext(ctx context.Context) service.BearerToken { - if v, ok := ctx.Value(implementations.BearerToken).(service.BearerToken); ok { + if v, ok := ctx.Value(storagegroup.BearerToken).(service.BearerToken); ok { return v } @@ -542,7 +541,7 @@ func bearerFromContext(ctx context.Context) service.BearerToken { } func extendedHeadersFromContext(ctx context.Context) []service.ExtendedHeader { - if v, ok := ctx.Value(implementations.ExtendedHeaders).([]service.ExtendedHeader); ok { + if v, ok := ctx.Value(storagegroup.ExtendedHeaders).([]service.ExtendedHeader); ok { return v } diff --git a/services/public/object/head_test.go b/pkg/network/transport/object/grpc/head_test.go similarity index 95% rename from services/public/object/head_test.go rename to pkg/network/transport/object/grpc/head_test.go index fcf2be7bb..50da24341 100644 --- a/services/public/object/head_test.go +++ b/pkg/network/transport/object/grpc/head_test.go @@ -8,9 +8,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/transformer" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/pkg/errors" "github.com/stretchr/testify/require" "go.uber.org/zap" ) @@ -215,7 +215,7 @@ func Test_objectService_Head(t *testing.T) { t.Run("request handler error", func(t *testing.T) { // create custom error for test - rhErr := internal.Error("test error for request handler") + rhErr := errors.New("test error for request handler") // create custom request for test req := new(object.HeadRequest) @@ -277,7 +277,7 @@ func Test_coreHeadReceiver_head(t *testing.T) { hInfo.setRaw(true) // create custom error for test - srErr := internal.Error("test error for straight object receiver") + srErr := errors.New("test error for straight object receiver") s := &coreObjectReceiver{ straightObjRecv: &testHeadEntity{ @@ -343,7 +343,7 @@ func Test_coreHeadReceiver_head(t *testing.T) { s := &coreObjectReceiver{ straightObjRecv: &testHeadEntity{ - err: internal.Error(""), // force straightObjectReceiver to return non-empty error + err: errors.New(""), // force straightObjectReceiver to return non-empty error }, childLister: &testHeadEntity{ f: func(items ...interface{}) { @@ -368,7 +368,7 @@ func Test_coreHeadReceiver_head(t *testing.T) { t.Run("correct result", func(t *testing.T) { var ( childCount = 5 - rErr = internal.Error("test error for rewinding receiver") + rErr = errors.New("test error for rewinding receiver") children = make([]ID, 0, childCount) ) @@ -425,7 +425,7 @@ func Test_coreHeadReceiver_head(t *testing.T) { s := &coreObjectReceiver{ straightObjRecv: &testHeadEntity{ - err: internal.Error(""), // force straight receiver to return non-nil error + err: errors.New(""), // force straight receiver to return non-nil error }, ancestralRecv: &testHeadEntity{ f: func(items ...interface{}) { @@ -456,7 +456,7 @@ func Test_straightHeadReceiver_head(t *testing.T) { hInfo.setFullHeaders(true) t.Run("executor error", func(t *testing.T) { - exErr := internal.Error("test error for operation executor") + exErr := errors.New("test error for operation executor") s := &straightObjectReceiver{ executor: &testHeadEntity{ @@ -501,7 +501,7 @@ func Test_coreObjectRewinder_rewind(t *testing.T) { ctx := context.TODO() t.Run("transformer failure", func(t *testing.T) { - tErr := internal.Error("test error for object transformer") + tErr := errors.New("test error for object transformer") objs := []Object{*new(Object), *new(Object)} s := &coreObjectRewinder{ diff --git a/services/public/object/implementations.go b/pkg/network/transport/object/grpc/implementations.go similarity index 86% rename from services/public/object/implementations.go rename to pkg/network/transport/object/grpc/implementations.go index 2f4b3715a..c63f3519b 100644 --- a/services/public/object/implementations.go +++ b/pkg/network/transport/object/grpc/implementations.go @@ -5,7 +5,7 @@ import ( "github.com/multiformats/go-multiaddr" "github.com/nspcc-dev/neofs-api-go/object" - "github.com/nspcc-dev/neofs-node/lib/peers" + "github.com/nspcc-dev/neofs-node/pkg/network/peers" "github.com/pkg/errors" ) @@ -23,7 +23,7 @@ func NewRemoteService(ps peers.Interface) RemoteService { } func (rs remoteService) Remote(ctx context.Context, addr multiaddr.Multiaddr) (object.ServiceClient, error) { - con, err := rs.ps.GRPCConnection(ctx, addr, false) + con, err := rs.ps.GRPCConnection(ctx, addr) if err != nil { return nil, errors.Wrapf(err, "remoteService.Remote failed on GRPCConnection to %s", addr) } diff --git a/services/public/object/listing.go b/pkg/network/transport/object/grpc/listing.go similarity index 89% rename from services/public/object/listing.go rename to pkg/network/transport/object/grpc/listing.go index 9967fc543..8d3f7c866 100644 --- a/services/public/object/listing.go +++ b/pkg/network/transport/object/grpc/listing.go @@ -8,10 +8,8 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" v1 "github.com/nspcc-dev/neofs-api-go/query" "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/objio" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/pkg/errors" "go.uber.org/zap" ) @@ -47,24 +45,16 @@ type ( } selectiveRangeRecv struct { - executor implementations.SelectiveContainerExecutor + executor transport.SelectiveContainerExecutor } ) const ( lmQueryMarshalFail = "marshal search query failure" lmListFail = "searching inside children listing failure" - - errRelationNotFound = internal.Error("relation not found") ) -var ( - _ relationQueryFunc = coreChildrenQueryFunc - _ transport.SearchInfo = (*rawSearchInfo)(nil) - _ objectChildrenLister = (*coreChildrenLister)(nil) - _ objio.RelativeReceiver = (*neighborReceiver)(nil) - _ selectiveRangeReceiver = (*selectiveRangeRecv)(nil) -) +var errRelationNotFound = errors.New("relation not found") func (s *neighborReceiver) Base(ctx context.Context, addr Address) (RangeDescriptor, error) { if res, err := s.rangeDescRecv.rangeDescriptor(ctx, addr, s.firstChildQueryFn); err == nil { @@ -87,9 +77,9 @@ func (s *neighborReceiver) Neighbor(ctx context.Context, addr Address, left bool func (s *selectiveRangeRecv) rangeDescriptor(ctx context.Context, addr Address, fn relationQueryFunc) (res RangeDescriptor, err error) { b := false - p := &implementations.HeadParams{ - GetParams: implementations.GetParams{ - SelectiveParams: implementations.SelectiveParams{ + p := &transport.HeadParams{ + GetParams: transport.GetParams{ + SelectiveParams: transport.SelectiveParams{ CID: addr.CID, ServeLocal: true, TTL: service.SingleForwardingTTL, diff --git a/services/public/object/listing_test.go b/pkg/network/transport/object/grpc/listing_test.go similarity index 91% rename from services/public/object/listing_test.go rename to pkg/network/transport/object/grpc/listing_test.go index 7c27ca794..614237af0 100644 --- a/services/public/object/listing_test.go +++ b/pkg/network/transport/object/grpc/listing_test.go @@ -8,10 +8,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/query" v1 "github.com/nspcc-dev/neofs-api-go/query" "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/test" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/nspcc-dev/neofs-node/pkg/util/logger/test" + "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -21,7 +20,7 @@ type ( // If err is not nil, it returns as it is. Otherwise, casted to needed type res returns w/o error. testListingEntity struct { // Set of interfaces which entity must implement, but some methods from those does not call. - implementations.SelectiveContainerExecutor + transport.SelectiveContainerExecutor // Argument interceptor. Used for ascertain of correct parameter passage between components. f func(...interface{}) @@ -36,7 +35,7 @@ var ( _ objectSearcher = (*testListingEntity)(nil) _ selectiveRangeReceiver = (*testListingEntity)(nil) - _ implementations.SelectiveContainerExecutor = (*testListingEntity)(nil) + _ transport.SelectiveContainerExecutor = (*testListingEntity)(nil) ) func (s *testListingEntity) rangeDescriptor(_ context.Context, a Address, f relationQueryFunc) (RangeDescriptor, error) { @@ -49,7 +48,7 @@ func (s *testListingEntity) rangeDescriptor(_ context.Context, a Address, f rela return s.res.(RangeDescriptor), nil } -func (s *testListingEntity) Head(_ context.Context, p *implementations.HeadParams) error { +func (s *testListingEntity) Head(_ context.Context, p *transport.HeadParams) error { if s.f != nil { s.f(p) } @@ -142,9 +141,9 @@ func Test_coreChildrenLister_children(t *testing.T) { t.Run("correct query function params", func(t *testing.T) { require.Equal(t, addr, v) }) - return nil, internal.Error("") // force relationQueryFunc to return some non-nil error + return nil, errors.New("") // force relationQueryFunc to return some non-nil error }, - log: test.NewTestLogger(false), + log: test.NewLogger(false), } require.Empty(t, s.children(ctx, addr)) @@ -152,7 +151,7 @@ func Test_coreChildrenLister_children(t *testing.T) { t.Run("object searcher failure", func(t *testing.T) { // create custom timeout for test - sErr := internal.Error("test error for object searcher") + sErr := errors.New("test error for object searcher") // create custom timeout for test timeout := 3 * time.Second // create custom query for test @@ -174,7 +173,7 @@ func Test_coreChildrenLister_children(t *testing.T) { }, err: sErr, // force objectSearcher to return sErr }, - log: test.NewTestLogger(false), + log: test.NewLogger(false), timeout: timeout, } @@ -295,7 +294,7 @@ func Test_selectiveRangeRecv(t *testing.T) { addr := testObjectAddress(t) t.Run("query function failure", func(t *testing.T) { - qfErr := internal.Error("test error for query function") + qfErr := errors.New("test error for query function") _, err := new(selectiveRangeRecv).rangeDescriptor(ctx, testObjectAddress(t), func(Address) ([]byte, error) { return nil, qfErr }) @@ -309,7 +308,7 @@ func Test_selectiveRangeRecv(t *testing.T) { s := &selectiveRangeRecv{ executor: &testListingEntity{ f: func(items ...interface{}) { - p := items[0].(*implementations.HeadParams) + p := items[0].(*transport.HeadParams) require.Equal(t, addr.CID, p.CID) require.True(t, p.ServeLocal) require.Equal(t, uint32(service.SingleForwardingTTL), p.TTL) @@ -327,7 +326,7 @@ func Test_selectiveRangeRecv(t *testing.T) { s := &selectiveRangeRecv{ executor: &testListingEntity{ f: func(items ...interface{}) { - p := items[0].(*implementations.HeadParams) + p := items[0].(*transport.HeadParams) require.Equal(t, addr.CID, p.CID) require.True(t, p.ServeLocal) require.Equal(t, uint32(service.SingleForwardingTTL), p.TTL) @@ -345,7 +344,7 @@ func Test_selectiveRangeRecv(t *testing.T) { t.Run("correct result", func(t *testing.T) { t.Run("failure", func(t *testing.T) { t.Run("executor failure", func(t *testing.T) { - exErr := internal.Error("test error for executor") + exErr := errors.New("test error for executor") s := &selectiveRangeRecv{ executor: &testListingEntity{ @@ -382,7 +381,7 @@ func Test_selectiveRangeRecv(t *testing.T) { executor: &testListingEntity{ SelectiveContainerExecutor: nil, f: func(items ...interface{}) { - p := items[0].(*implementations.HeadParams) + p := items[0].(*transport.HeadParams) p.Handler(nil, obj) }, }, @@ -426,7 +425,7 @@ func Test_neighborReceiver(t *testing.T) { require.Equal(t, addr, items[0]) _, _ = items[1].(relationQueryFunc)(addr) }, - err: internal.Error(""), + err: errors.New(""), }, } @@ -442,7 +441,7 @@ func Test_neighborReceiver(t *testing.T) { }) t.Run("correct result", func(t *testing.T) { - rErr := internal.Error("test error for range receiver") + rErr := errors.New("test error for range receiver") rngRecv := &testListingEntity{err: rErr} s := &neighborReceiver{rangeDescRecv: rngRecv} @@ -494,7 +493,7 @@ func Test_neighborReceiver(t *testing.T) { t.Run("first child doesn't exist", func(t *testing.T) { called := false - recv := &testListingEntity{err: internal.Error("")} + recv := &testListingEntity{err: errors.New("")} recv.f = func(...interface{}) { if called { diff --git a/services/public/object/postprocessor.go b/pkg/network/transport/object/grpc/postprocessor.go similarity index 100% rename from services/public/object/postprocessor.go rename to pkg/network/transport/object/grpc/postprocessor.go diff --git a/services/public/object/postprocessor_test.go b/pkg/network/transport/object/grpc/postprocessor_test.go similarity index 95% rename from services/public/object/postprocessor_test.go rename to pkg/network/transport/object/grpc/postprocessor_test.go index f114fc982..ad48fb85c 100644 --- a/services/public/object/postprocessor_test.go +++ b/pkg/network/transport/object/grpc/postprocessor_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/nspcc-dev/neofs-node/internal" + "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -48,7 +48,7 @@ func TestComplexPostProcessor_PostProcess(t *testing.T) { req := new(testPostProcessorEntity) // create custom error - pErr := internal.Error("test error for post processor") + pErr := errors.New("test error for post processor") // create list of post processors postProcCount := 10 diff --git a/services/public/object/preprocessor.go b/pkg/network/transport/object/grpc/preprocessor.go similarity index 97% rename from services/public/object/preprocessor.go rename to pkg/network/transport/object/grpc/preprocessor.go index 620cae5a2..7b3291dfb 100644 --- a/services/public/object/preprocessor.go +++ b/pkg/network/transport/object/grpc/preprocessor.go @@ -94,8 +94,6 @@ func newPreProcessor(p *Params) requestPreProcessor { aclInfoReceiver: p.aclInfoReceiver, - basicChecker: p.BasicACLChecker, - reqActionCalc: p.requestActionCalculator, localStore: p.LocalStore, @@ -109,7 +107,7 @@ func newPreProcessor(p *Params) requestPreProcessor { }, new(bearerSignatureVerifier), &bearerOwnershipVerifier{ - cnrOwnerChecker: p.ACLHelper, + cnrStorage: p.ContainerStorage, }, }, }, @@ -145,7 +143,6 @@ func newPreProcessor(p *Params) requestPreProcessor { }, &tokenPreProcessor{ - keyVerifier: p.OwnerKeyVerifier, staticVerifier: newComplexTokenVerifier( &tokenEpochsVerifier{ epochRecv: p.EpochReceiver, diff --git a/services/public/object/preprocessor_test.go b/pkg/network/transport/object/grpc/preprocessor_test.go similarity index 90% rename from services/public/object/preprocessor_test.go rename to pkg/network/transport/object/grpc/preprocessor_test.go index 7a1509285..bdadc95ff 100644 --- a/services/public/object/preprocessor_test.go +++ b/pkg/network/transport/object/grpc/preprocessor_test.go @@ -5,10 +5,9 @@ import ( "testing" "github.com/nspcc-dev/neofs-api-go/object" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/test" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/replication/storage" + "github.com/nspcc-dev/neofs-node/pkg/util/test" + "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -20,9 +19,8 @@ type ( // Set of interfaces which testCommonEntity must implement, but some methods from those does not call. serviceRequest Placer - implementations.AddressStoreComponent + storage.AddressStoreComponent EpochReceiver - core.OwnerKeyVerifier // Argument interceptor. Used for ascertain of correct parameter passage between components. f func(...interface{}) @@ -48,7 +46,7 @@ func TestSigningPreProcessor_preProcess(t *testing.T) { req := new(object.SearchRequest) t.Run("internal pre-processor error", func(t *testing.T) { - ppErr := internal.Error("test error for pre-processor") + ppErr := errors.New("test error for pre-processor") s := &signingPreProcessor{ preProc: &testPreProcessorEntity{ @@ -109,7 +107,7 @@ func TestComplexPreProcessor_PreProcess(t *testing.T) { } // create custom error - pErr := internal.Error("pre processor error for test") + pErr := errors.New("pre processor error for test") p2 := &testPreProcessorEntity{ err: pErr, // force second requestPreProcessor to return created error } diff --git a/services/public/object/put.go b/pkg/network/transport/object/grpc/put.go similarity index 86% rename from services/public/object/put.go rename to pkg/network/transport/object/grpc/put.go index b1cc519ca..404445f4c 100644 --- a/services/public/object/put.go +++ b/pkg/network/transport/object/grpc/put.go @@ -10,12 +10,11 @@ import ( "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-api-go/service" "github.com/nspcc-dev/neofs-api-go/session" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/objutil" - "github.com/nspcc-dev/neofs-node/lib/transformer" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport/storagegroup" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/verifier" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -32,7 +31,7 @@ type ( receivingObjectStorer struct { straightStorer objectStorer - vPayload objutil.Verifier + vPayload verifier.Verifier } filteringObjectStorer struct { @@ -90,32 +89,20 @@ type transformerHandlerErr struct { error } -const ( - errObjectExpected = internal.Error("missing object") - errChunkExpected = internal.Error("empty chunk received") +var ( + errObjectExpected = errors.New("missing object") + errChunkExpected = errors.New("empty chunk received") ) -const ( - errMissingOwnerKeys = internal.Error("missing owner keys") - errBrokenToken = internal.Error("broken token structure") - errNilToken = internal.Error("missing session token") - errWrongTokenAddress = internal.Error("wrong object address in token") -) - -const errTransformer = internal.Error("could not transform the object") - var ( - _ transport.PutInfo = (*rawPutInfo)(nil) - _ addressAccumulator = (*coreAddrAccum)(nil) - _ objectStorer = (*straightObjectStorer)(nil) - _ transport.PutInfo = (*putRequest)(nil) - _ io.Reader = (*putStreamReader)(nil) - _ objectStorer = (*filteringObjectStorer)(nil) - _ objectStorer = (*transformingObjectStorer)(nil) - _ objectStorer = (*tokenObjectStorer)(nil) - _ objectStorer = (*receivingObjectStorer)(nil) + errMissingOwnerKeys = errors.New("missing owner keys") + errBrokenToken = errors.New("broken token structure") + errNilToken = errors.New("missing session token") + errWrongTokenAddress = errors.New("wrong object address in token") ) +var errTransformer = errors.New("could not transform the object") + func (s *objectService) Put(srv object.Service_PutServer) (err error) { defer func() { if r := recover(); r != nil { @@ -210,8 +197,8 @@ func (s *tokenObjectStorer) putObject(ctx context.Context, info transport.PutInf contextWithValues(ctx, transformer.PrivateSessionToken, pToken, transformer.PublicSessionToken, token, - implementations.BearerToken, info.GetBearerToken(), - implementations.ExtendedHeaders, info.ExtendedHeaders(), + storagegroup.BearerToken, info.GetBearerToken(), + storagegroup.ExtendedHeaders, info.ExtendedHeaders(), ), info, ) diff --git a/services/public/object/put_test.go b/pkg/network/transport/object/grpc/put_test.go similarity index 95% rename from services/public/object/put_test.go rename to pkg/network/transport/object/grpc/put_test.go index 80fe33838..1bdd58803 100644 --- a/services/public/object/put_test.go +++ b/pkg/network/transport/object/grpc/put_test.go @@ -11,12 +11,11 @@ import ( "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-api-go/service" "github.com/nspcc-dev/neofs-api-go/session" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/test" - "github.com/nspcc-dev/neofs-node/lib/transformer" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/nspcc-dev/neofs-node/pkg/util/test" + "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -30,7 +29,7 @@ type ( transport.PutInfo Filter session.PrivateTokenStore - implementations.SelectiveContainerExecutor + transport.SelectiveContainerExecutor // Argument interceptor. Used for ascertain of correct parameter passage between components. f func(...interface{}) @@ -149,7 +148,7 @@ func Test_objectService_Put(t *testing.T) { t.Run("stream error", func(t *testing.T) { // create custom error for test - psErr := internal.Error("test error for put stream server") + psErr := errors.New("test error for put stream server") s := &testPutEntity{ err: psErr, // force server to return psErr @@ -175,7 +174,7 @@ func Test_objectService_Put(t *testing.T) { }} // create custom error for test - hErr := internal.Error("test error for request handler") + hErr := errors.New("test error for request handler") srv := &testPutEntity{ res: req, // force server to return req @@ -212,7 +211,7 @@ func Test_straightObjectStorer_putObject(t *testing.T) { t.Run("executor error", func(t *testing.T) { // create custom error for test - exErr := internal.Error("test error for operation executor") + exErr := errors.New("test error for operation executor") // create custom meta info for test info := new(testPutEntity) @@ -259,7 +258,7 @@ func Test_straightObjectStorer_putObject(t *testing.T) { func Test_recvPutHeaderMsg(t *testing.T) { t.Run("server error", func(t *testing.T) { // create custom error for test - srvErr := internal.Error("test error for put server") + srvErr := errors.New("test error for put server") srv := &testPutEntity{ err: srvErr, // force put server to return srvErr @@ -453,7 +452,7 @@ func Test_bifurcatingObjectStorer(t *testing.T) { ctx := context.TODO() // create custom error for test - sErr := internal.Error("test error for object storer") + sErr := errors.New("test error for object storer") t.Run("w/ token", func(t *testing.T) { // create custom request w/ token @@ -562,7 +561,7 @@ func Test_tokenObjectStorer(t *testing.T) { t.Run("token store failure", func(t *testing.T) { s := &tokenObjectStorer{ tokenStore: &testPutEntity{ - err: internal.Error(""), // force token store to return a non-nil error + err: errors.New(""), // force token store to return a non-nil error }, } @@ -676,7 +675,7 @@ func Test_receivingObjectStorer(t *testing.T) { }) t.Run("payload verification failure", func(t *testing.T) { - vErr := internal.Error("payload verification error for test") + vErr := errors.New("payload verification error for test") req := newRawPutInfo() req.setHead(&Object{ @@ -752,7 +751,7 @@ func Test_transformingObjectStorer(t *testing.T) { t.Run("correct behavior", func(t *testing.T) { var ( - tErr = internal.Error("test error for transformer") + tErr = errors.New("test error for transformer") addr = testObjectAddress(t) obj = &Object{ SystemHeader: SystemHeader{ @@ -804,7 +803,7 @@ func Test_transformingObjectStorer(t *testing.T) { require.Equal(t, exp, items[0]) }) }, - err: internal.Error(""), + err: errors.New(""), }, mErr: map[error]struct{}{ tErr: {}, @@ -820,13 +819,13 @@ func Test_transformingObjectStorer(t *testing.T) { _, err = s.putObject(ctx, req) require.EqualError(t, err, tErr.Error()) - tr.err = internal.Error("some other error") + tr.err = errors.New("some other error") _, err = s.putObject(ctx, req) require.EqualError(t, err, errTransformer.Error()) e := &transformerHandlerErr{ - error: internal.Error("transformer handler error"), + error: errors.New("transformer handler error"), } tr.err = e @@ -863,7 +862,7 @@ func Test_putStreamReader(t *testing.T) { t.Run("receive message failure", func(t *testing.T) { t.Run("stream problem", func(t *testing.T) { - srvErr := internal.Error("test error for stream server") + srvErr := errors.New("test error for stream server") s := &putStreamReader{ srv: &testPutEntity{ diff --git a/services/public/object/query.go b/pkg/network/transport/object/grpc/query.go similarity index 92% rename from services/public/object/query.go rename to pkg/network/transport/object/grpc/query.go index 79fddde47..12dea3ee2 100644 --- a/services/public/object/query.go +++ b/pkg/network/transport/object/grpc/query.go @@ -7,9 +7,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/query" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/pkg/errors" "go.uber.org/zap" ) @@ -36,13 +36,13 @@ const ( queryFilterName = "QUERY_FILTER" pmUndefinedFilterType = "undefined filter type %d" - - errUnsupportedQueryVersion = internal.Error("unsupported query version number") ) -const errSearchQueryUnmarshal = internal.Error("query unmarshal failure") +var errUnsupportedQueryVersion = errors.New("unsupported query version number") -const errLocalQueryImpose = internal.Error("local query imposing failure") +var errSearchQueryUnmarshal = errors.New("query unmarshal failure") + +var errLocalQueryImpose = errors.New("local query imposing failure") var ( _ filterCreator = (*coreFilterCreator)(nil) diff --git a/services/public/object/query_test.go b/pkg/network/transport/object/grpc/query_test.go similarity index 98% rename from services/public/object/query_test.go rename to pkg/network/transport/object/grpc/query_test.go index ee6a5dea3..8e1a17dda 100644 --- a/services/public/object/query_test.go +++ b/pkg/network/transport/object/grpc/query_test.go @@ -9,9 +9,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/query" "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-api-go/storagegroup" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/pkg/errors" "github.com/stretchr/testify/require" "go.uber.org/zap" ) @@ -96,7 +96,7 @@ func Test_queryVersionController_imposeQuery(t *testing.T) { require.Equal(t, 0, items[2].(int)) }) }, - err: internal.Error(""), // just to prevent panic + err: errors.New(""), // just to prevent panic } _, _ = qImp.imposeQuery(ctx, cid, qData, 0) @@ -107,7 +107,7 @@ func Test_queryVersionController_imposeQuery(t *testing.T) { m := make(map[int]localQueryImposer) qImp := &queryVersionController{m: m} - impErr := internal.Error("test error for query imposer") + impErr := errors.New("test error for query imposer") m[0] = &testQueryEntity{ err: impErr, // force localQueryImposer to return impErr @@ -208,7 +208,7 @@ func Test_coreQueryImposer_imposeQuery(t *testing.T) { t.Run("listing error", func(t *testing.T) { // create new error for test - lsErr := internal.Error("test error of local store listing") + lsErr := errors.New("test error of local store listing") // create test query imposer with mocked always failing lister qImposer := &coreQueryImposer{ @@ -241,7 +241,7 @@ func Test_coreQueryImposer_imposeQuery(t *testing.T) { // ascertain that argument is as expected require.Equal(t, fc, p[0].(Filter)) }, - err: internal.Error(""), + err: errors.New(""), }, log: log, } diff --git a/lib/objio/range.go b/pkg/network/transport/object/grpc/range/range.go similarity index 99% rename from lib/objio/range.go rename to pkg/network/transport/object/grpc/range/range.go index 183fb7398..864888ff2 100644 --- a/lib/objio/range.go +++ b/pkg/network/transport/object/grpc/range/range.go @@ -1,4 +1,4 @@ -package objio +package _range import ( "context" @@ -6,7 +6,7 @@ import ( "sync" "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/nspcc-dev/neofs-node/lib/localstore" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" "github.com/pkg/errors" ) diff --git a/lib/objio/range_test.go b/pkg/network/transport/object/grpc/range/range_test.go similarity index 99% rename from lib/objio/range_test.go rename to pkg/network/transport/object/grpc/range/range_test.go index 6d7290d94..82ec3d32f 100644 --- a/lib/objio/range_test.go +++ b/pkg/network/transport/object/grpc/range/range_test.go @@ -1,4 +1,4 @@ -package objio +package _range import ( "context" diff --git a/services/public/object/ranges.go b/pkg/network/transport/object/grpc/ranges.go similarity index 89% rename from services/public/object/ranges.go rename to pkg/network/transport/object/grpc/ranges.go index acc05e0cb..68196dfea 100644 --- a/services/public/object/ranges.go +++ b/pkg/network/transport/object/grpc/ranges.go @@ -8,9 +8,8 @@ import ( "github.com/nspcc-dev/neofs-api-go/hash" "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/objio" - "github.com/nspcc-dev/neofs-node/lib/transport" + _range "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc/range" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -22,11 +21,11 @@ type ( // RangeDescriptor is a type alias of // RangeDescriptor from objio package. - RangeDescriptor = objio.RangeDescriptor + RangeDescriptor = _range.RangeDescriptor // RangeChopper is a type alias of // RangeChopper from objio package. - RangeChopper = objio.RangeChopper + RangeChopper = _range.RangeChopper // GetRangeRequest is a type alias of // GetRangeRequest from object package of neofs-api-go. @@ -99,8 +98,8 @@ type ( } coreRngRevealer struct { - relativeRecv objio.RelativeReceiver - chopTable objio.ChopperTable + relativeRecv _range.RelativeReceiver + chopTable _range.ChopperTable } getRangeServerWriter struct { @@ -116,22 +115,9 @@ const ( emGetRangeFail = "could get object range #%d part #%d" emRangeRevealFail = "could not reveal object range #%d" emRangeCollect = "could not collect result of object range #%d" - - errRangeReveal = internal.Error("could not reveal payload range") ) -var ( - _ transport.RangeInfo = (*rawRangeInfo)(nil) - _ rangeTool = (*rawRangeHashInfo)(nil) - _ rangeTool = (*transportRequest)(nil) - _ rangeItemAccumulator = (*rangeHashAccum)(nil) - _ rangeItemAccumulator = (*singleItemHandler)(nil) - _ rangeRevealer = (*coreRngRevealer)(nil) - _ objectRangeReceiver = (*coreRangeReceiver)(nil) - _ objectRangeReceiver = (*straightRangeReceiver)(nil) - _ io.Writer = (*getRangeServerWriter)(nil) - _ transport.RangeInfo = (*transportRequest)(nil) -) +var errRangeReveal = errors.New("could not reveal payload range") func (s *objectService) GetRange(req *GetRangeRequest, srv object.Service_GetRangeServer) (err error) { defer func() { @@ -286,11 +272,11 @@ func (s *coreRngRevealer) reveal(ctx context.Context, r *RangeDescriptor) ([]Ran } func (s *coreRngRevealer) getChopper(addr Address) (res RangeChopper, err error) { - if res, err = s.chopTable.GetChopper(addr, objio.RCCharybdis); err == nil && res.Closed() { + if res, err = s.chopTable.GetChopper(addr, _range.RCCharybdis); err == nil && res.Closed() { return - } else if res, err = s.chopTable.GetChopper(addr, objio.RCScylla); err == nil { + } else if res, err = s.chopTable.GetChopper(addr, _range.RCScylla); err == nil { return - } else if res, err = objio.NewScylla(&objio.ChopperParams{ + } else if res, err = _range.NewScylla(&_range.ChopperParams{ RelativeReceiver: s.relativeRecv, Addr: addr, }); err != nil { diff --git a/services/public/object/ranges_test.go b/pkg/network/transport/object/grpc/ranges_test.go similarity index 94% rename from services/public/object/ranges_test.go rename to pkg/network/transport/object/grpc/ranges_test.go index 57d6d2e82..065846be3 100644 --- a/services/public/object/ranges_test.go +++ b/pkg/network/transport/object/grpc/ranges_test.go @@ -9,9 +9,9 @@ import ( "github.com/nspcc-dev/neofs-api-go/hash" "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/service" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/objio" - "github.com/nspcc-dev/neofs-node/lib/transport" + _range "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc/range" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/pkg/errors" "github.com/stretchr/testify/require" "go.uber.org/zap" ) @@ -35,7 +35,7 @@ type ( ) var ( - _ objio.RelativeReceiver = (*testRangeEntity)(nil) + _ _range.RelativeReceiver = (*testRangeEntity)(nil) _ RangeChopper = (*testRangeEntity)(nil) _ operationExecutor = (*testRangeEntity)(nil) _ requestHandler = (*testRangeEntity)(nil) @@ -117,7 +117,7 @@ func (s *testRangeEntity) PutChopper(addr Address, chopper RangeChopper) error { return s.err } -func (s *testRangeEntity) GetChopper(addr Address, rc objio.RCType) (RangeChopper, error) { +func (s *testRangeEntity) GetChopper(addr Address, rc _range.RCType) (RangeChopper, error) { if s.f != nil { s.f(addr, rc) } @@ -145,7 +145,7 @@ func Test_objectService_GetRange(t *testing.T) { req := &GetRangeRequest{Address: testObjectAddress(t)} t.Run("request handler error", func(t *testing.T) { - rhErr := internal.Error("test error for request handler") + rhErr := errors.New("test error for request handler") s := &objectService{ statusCalculator: newStatusCalculator(), @@ -202,7 +202,7 @@ func Test_objectService_GetRangeHash(t *testing.T) { req := &GetRangeHashRequest{Address: testObjectAddress(t)} t.Run("request handler error", func(t *testing.T) { - rhErr := internal.Error("test error for request handler") + rhErr := errors.New("test error for request handler") s := &objectService{ statusCalculator: newStatusCalculator(), @@ -259,7 +259,7 @@ func Test_coreRangeReceiver(t *testing.T) { log := zap.L() t.Run("range reveal failure", func(t *testing.T) { - revErr := internal.Error("test error for range revealer") + revErr := errors.New("test error for range revealer") rt := newRawRangeHashInfo() rt.setTTL(service.NonForwardingTTL) @@ -300,7 +300,7 @@ func Test_coreRangeReceiver(t *testing.T) { }) t.Run("get sub range failure", func(t *testing.T) { - gErr := internal.Error("test error for get range") + gErr := errors.New("test error for get range") rt := newRawRangeHashInfo() rt.setTTL(service.NonForwardingTTL) @@ -419,7 +419,7 @@ func Test_straightRangeReceiver_getRange(t *testing.T) { req := new(transportRequest) t.Run("executor error", func(t *testing.T) { - exErr := internal.Error("test error for executor") + exErr := errors.New("test error for executor") s := &straightRangeReceiver{ executor: &testRangeEntity{ @@ -466,14 +466,14 @@ func Test_coreRngRevealer_reveal(t *testing.T) { } t.Run("charybdis chopper presence", func(t *testing.T) { - cErr := internal.Error("test error for charybdis") + cErr := errors.New("test error for charybdis") s := &coreRngRevealer{ chopTable: &testRangeEntity{ f: func(items ...interface{}) { t.Run("correct chopper table params", func(t *testing.T) { require.Equal(t, rd.Addr, items[0]) - require.Equal(t, objio.RCCharybdis, items[1]) + require.Equal(t, _range.RCCharybdis, items[1]) }) }, res: &testRangeEntity{ @@ -496,7 +496,7 @@ func Test_coreRngRevealer_reveal(t *testing.T) { }) t.Run("scylla chopper presence", func(t *testing.T) { - scErr := internal.Error("test error for scylla") + scErr := errors.New("test error for scylla") scylla := &testRangeEntity{ err: scErr, // force RangeChopper to return scErr @@ -505,8 +505,8 @@ func Test_coreRngRevealer_reveal(t *testing.T) { ct := new(testRangeEntity) ct.f = func(items ...interface{}) { - if items[1].(objio.RCType) == objio.RCCharybdis { - ct.err = internal.Error("") + if items[1].(_range.RCType) == _range.RCCharybdis { + ct.err = errors.New("") } else { ct.res = scylla ct.err = nil @@ -527,7 +527,7 @@ func Test_coreRngRevealer_reveal(t *testing.T) { s := &coreRngRevealer{ relativeRecv: nil, // pass empty relation receiver to fail constructor chopTable: &testRangeEntity{ - err: internal.Error(""), // force ChopperTable to return non-nil error + err: errors.New(""), // force ChopperTable to return non-nil error }, } @@ -537,13 +537,13 @@ func Test_coreRngRevealer_reveal(t *testing.T) { }) t.Run("success", func(t *testing.T) { - rrErr := internal.Error("test error for relative receiver") + rrErr := errors.New("test error for relative receiver") relRecv := &testRangeEntity{ err: rrErr, // force relative receiver to return rrErr } - scylla, err := objio.NewScylla(&objio.ChopperParams{ + scylla, err := _range.NewScylla(&_range.ChopperParams{ RelativeReceiver: relRecv, Addr: rd.Addr, }) @@ -562,7 +562,7 @@ func Test_coreRngRevealer_reveal(t *testing.T) { } }) }, - err: internal.Error(""), // force ChopperTable to return non-nil error + err: errors.New(""), // force ChopperTable to return non-nil error }, } diff --git a/services/public/object/response.go b/pkg/network/transport/object/grpc/response.go similarity index 92% rename from services/public/object/response.go rename to pkg/network/transport/object/grpc/response.go index 37f086764..8e08941f8 100644 --- a/services/public/object/response.go +++ b/pkg/network/transport/object/grpc/response.go @@ -3,9 +3,9 @@ package object import ( "context" - "github.com/nspcc-dev/neofs-api-go/acl" + eacl "github.com/nspcc-dev/neofs-api-go/acl/extended" "github.com/nspcc-dev/neofs-api-go/object" - libacl "github.com/nspcc-dev/neofs-node/lib/acl" + eaclstorage "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" ) type ( @@ -27,7 +27,7 @@ type complexResponsePreparer struct { } type aclResponsePreparer struct { - eaclSrc libacl.ExtendedACLSource + eaclSrc eaclstorage.Storage aclInfoReceiver aclInfoReceiver @@ -95,7 +95,7 @@ func (s *aclResponsePreparer) prepareResponse(ctx context.Context, req serviceRe objHdrSrc: headersFromObject{ obj: obj, }, - target: aclInfo.target, + group: aclInfo.targetInfo.group, } if aclInfo.checkBearer { @@ -104,7 +104,7 @@ func (s *aclResponsePreparer) prepareResponse(ctx context.Context, req serviceRe } } - if action := s.reqActCalc.calculateRequestAction(ctx, p); action != acl.ActionAllow { + if action := s.reqActCalc.calculateRequestAction(ctx, p); action != eacl.ActionAllow { return errAccessDenied } diff --git a/services/public/object/response_test.go b/pkg/network/transport/object/grpc/response_test.go similarity index 100% rename from services/public/object/response_test.go rename to pkg/network/transport/object/grpc/response_test.go diff --git a/services/public/object/search.go b/pkg/network/transport/object/grpc/search.go similarity index 97% rename from services/public/object/search.go rename to pkg/network/transport/object/grpc/search.go index 39771ddd6..cb6b35d27 100644 --- a/services/public/object/search.go +++ b/pkg/network/transport/object/grpc/search.go @@ -6,7 +6,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" v1 "github.com/nspcc-dev/neofs-api-go/query" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" "go.uber.org/zap" ) diff --git a/services/public/object/search_test.go b/pkg/network/transport/object/grpc/search_test.go similarity index 95% rename from services/public/object/search_test.go rename to pkg/network/transport/object/grpc/search_test.go index dc65edef5..bd1e73679 100644 --- a/services/public/object/search_test.go +++ b/pkg/network/transport/object/grpc/search_test.go @@ -6,8 +6,8 @@ import ( "github.com/nspcc-dev/neofs-api-go/object" v1 "github.com/nspcc-dev/neofs-api-go/query" - "github.com/nspcc-dev/neofs-node/internal" - "github.com/nspcc-dev/neofs-node/lib/transport" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -140,7 +140,7 @@ func TestObjectService_Search(t *testing.T) { addrList := testAddrList(t, int(addrPerMsg)+5) t.Run("request handler failure", func(t *testing.T) { - rhErr := internal.Error("test error for request handler") + rhErr := errors.New("test error for request handler") s := &objectService{ statusCalculator: newStatusCalculator(), } @@ -158,7 +158,7 @@ func TestObjectService_Search(t *testing.T) { }) t.Run("server error", func(t *testing.T) { - srvErr := internal.Error("test error for search server") + srvErr := errors.New("test error for search server") resp := &object.SearchResponse{Addresses: addrList[:addrPerMsg]} @@ -230,7 +230,7 @@ func Test_coreObjectSearcher(t *testing.T) { req.setQuery(testData(t, 10)) t.Run("operation executor failure", func(t *testing.T) { - execErr := internal.Error("test error for operation executor") + execErr := errors.New("test error for operation executor") s := &coreObjectSearcher{ executor: &testSearchEntity{ diff --git a/services/public/object/service.go b/pkg/network/transport/object/grpc/service.go similarity index 79% rename from services/public/object/service.go rename to pkg/network/transport/object/grpc/service.go index 87e120072..142a9ee9e 100644 --- a/services/public/object/service.go +++ b/pkg/network/transport/object/grpc/service.go @@ -13,16 +13,20 @@ import ( "github.com/nspcc-dev/neofs-api-go/service" "github.com/nspcc-dev/neofs-api-go/session" "github.com/nspcc-dev/neofs-api-go/storagegroup" - "github.com/nspcc-dev/neofs-node/internal" - libacl "github.com/nspcc-dev/neofs-node/lib/acl" - "github.com/nspcc-dev/neofs-node/lib/core" - "github.com/nspcc-dev/neofs-node/lib/implementations" - "github.com/nspcc-dev/neofs-node/lib/ir" - "github.com/nspcc-dev/neofs-node/lib/localstore" - "github.com/nspcc-dev/neofs-node/lib/objio" - "github.com/nspcc-dev/neofs-node/lib/objutil" - "github.com/nspcc-dev/neofs-node/lib/transformer" - "github.com/nspcc-dev/neofs-node/modules/grpc" + eaclstorage "github.com/nspcc-dev/neofs-node/pkg/core/container/acl/extended/storage" + "github.com/nspcc-dev/neofs-node/pkg/core/container/storage" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore" + "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper" + contract "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper" + "github.com/nspcc-dev/neofs-node/pkg/network/transport/grpc" + libgrpc "github.com/nspcc-dev/neofs-node/pkg/network/transport/grpc" + _range "github.com/nspcc-dev/neofs-node/pkg/network/transport/object/grpc/range" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" + storage2 "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/replication/storage" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport" + storagegroup2 "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport/storagegroup" + "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/verifier" "github.com/panjf2000/ants/v2" "github.com/pkg/errors" "go.uber.org/zap" @@ -127,6 +131,8 @@ type ( SetHeaders([]service.RequestExtendedHeader_KV) } + NetmapClient = wrapper.Wrapper + // Params groups the parameters of Object service server's constructor. Params struct { CheckACL bool @@ -143,12 +149,12 @@ type ( Placer Placer ObjectRestorer transformer.ObjectRestorer RemoteService RemoteService - AddressStore implementations.AddressStoreComponent + AddressStore storage2.AddressStoreComponent Logger *zap.Logger TokenStore session.PrivateTokenStore EpochReceiver EpochReceiver - implementations.ContainerNodesLister + PlacementWrapper *placement.PlacementWrapper DialTimeout time.Duration @@ -164,25 +170,21 @@ type ( headRecv objectReceiver - Verifier objutil.Verifier + Verifier verifier.Verifier Transformer transformer.Transformer MaxPayloadSize uint64 // ACL pre-processor params - ACLHelper implementations.ACLHelper - BasicACLChecker libacl.BasicChecker - IRStorage ir.Storage - ContainerLister implementations.ContainerNodesLister + ContainerStorage storage.Storage + NetmapClient *NetmapClient SGInfoReceiver storagegroup.InfoReceiver - OwnerKeyVerifier core.OwnerKeyVerifier + ExtendedACLSource eaclstorage.Storage - ExtendedACLSource libacl.ExtendedACLSource - - requestActionCalculator + requestActionCalculator requestActionCalculator targetFinder RequestTargeter @@ -199,7 +201,7 @@ type ( ls localstore.Localstore storageCap uint64 - executor implementations.SelectiveContainerExecutor + executor transport.SelectiveContainerExecutor pPut OperationParams pGet OperationParams @@ -244,32 +246,30 @@ const ( readyObjectsCheckpointFilterName = "READY_OBJECTS_PUT_CHECKPOINT" allObjectsCheckpointFilterName = "ALL_OBJECTS_PUT_CHECKPOINT" +) - errEmptyTokenStore = internal.Error("objectService.New failed: key store not provided") - errEmptyPlacer = internal.Error("objectService.New failed: placer not provided") - errEmptyTransformer = internal.Error("objectService.New failed: transformer pipeline not provided") - errEmptyGRPC = internal.Error("objectService.New failed: gRPC connector not provided") - errEmptyAddress = internal.Error("objectService.New failed: address store not provided") - errEmptyLogger = internal.Error("objectService.New failed: logger not provided") - errEmptyEpochReceiver = internal.Error("objectService.New failed: epoch receiver not provided") - errEmptyLocalStore = internal.Error("new local client failed: