diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 36fb2d01af..67191fde67 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @roman-khimov @notimetoname @cthulhu-rider +* @roman-khimov @carpawell @cthulhu-rider diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f144c69d36..9be2e82703 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -30,14 +30,6 @@ jobs: - name: Check out code uses: actions/checkout@v3 - - name: Cache go mod - uses: actions/cache@v3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go-${{ matrix.go }}- - - name: Run go test run: go test -coverprofile=coverage.txt -covermode=atomic ./... diff --git a/CHANGELOG.md b/CHANGELOG.md index 016e611c50..ef825fcb30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,16 @@ Changelog for NeoFS Node ### Added - Embedded Neo contracts in `contracts` dir (#2391) +- `dump-names` command for adm +- `renew-domain` command for adm ### Fixed +- `neo-go` RPC connection lost handling by IR (#1337) ### Removed +- Deprecated `morph.rpc_endpoint` SN and `morph.endpoint.client` IR config sections (#2400) +- `neofs_node_object_epoch` metric for IR and SN (#2347) +- Subnets support (#2411) ### Changed - CLI `--timeout` flag configures whole execution timeout from now (#2124) @@ -24,6 +30,18 @@ execution of commands with the `--await` flag and without an explicitly specified time period is now limited to 1 minute. This value can be changed with `--timeout` flag. +Deprecated `morph.rpc_endpoint` SN and `morph.endpoint.client` IR configurations +have been removed. Use `morph.endpoints` for both instead. +Deprecated `neofs_node_object_epoch` metric for IR and SN (the same for both) +has been removed. Use `neofs_node_state_epoch` for SN and `neofs_ir_state_epoch` +for IR instead. + +Subnets support has been removed: +- IR's `workers.subnet` and `contracts.subnet` configs are not used anymore. +- SN's `node.subnet` config section is not used anymore. +- `neoofs-amd morph` does not have `subnet` subcommand anymore. +- `neofs-cli container create` does not have `--subnet` flag anymore. + ## [0.37.0] - 2023-06-15 - Sogado ### Added @@ -56,21 +74,25 @@ specified time period is now limited to 1 minute. This value can be changed with - BoltDB (`go.etcd.io/bbolt`) to 1.3.7 ### Updating from v0.36.1 -- `neofs_node_object_epoch` metric for IR and SN (the same for both) has been deprecated and will be removed with the +`neofs_node_object_epoch` metric for IR and SN (the same for both) has been deprecated and will be removed with the next minor release. Use `neofs_node_state_epoch` for SN and `neofs_ir_state_epoch` for IR instead. -- Storage and Inner-ring nodes exposes their version via the `neofs_[node|ir]_version` metric now. -- In the local consensus mode (IR) it is allowed to provide additional TLS setup addresses now, see + +Storage and Inner-ring nodes exposes their version via the `neofs_[node|ir]_version` metric now. + +In the local consensus mode (IR) it is allowed to provide additional TLS setup addresses now, see `morph.consensus.rpc.tls` section. -- `morph.switch_interval` IR and SN config value is not used anymore. -- `morph.rpc_endpoint` SN config value and `morph.endpoint.client` IR config value has been deprecated and will be +`morph.switch_interval` IR and SN config value is not used anymore. +`morph.rpc_endpoint` SN config value and `morph.endpoint.client` IR config value has been deprecated and will be removed with the next minor release. Use `morph.endpoints` for both instead (NOTE: it does not have priorities now). -- If you're using binary keys with neofs-cli (`-w`), convert them to proper + +If you're using binary keys with neofs-cli (`-w`), convert them to proper NEP-6 wallets like this: $ xxd -p < path_to_binary.wallet # outputs hex-encoded key $ neofs-cli util keyer # outputs WIF $ neo-go wallet import -w --wif or just generate/use new keys. -- In local consensus mode `morph.validators` in IR's config can be omitted now, `morph.consensus.committee` will be used instead. + +In local consensus mode `morph.validators` in IR's config can be omitted now, `morph.consensus.committee` will be used instead. For detached consensus, it is a required config parameter now. ## [0.36.1] - 2023-04-26 @@ -455,10 +477,11 @@ Provide `--no-precheck` flag to `neofs-cli container set-eacl` for unconditional - `google.golang.org/grpc` to `v1.48.0` ### Updating from v0.30.0 -1. Change `morph.endpoint.client` priority values using the following rule: +Change `morph.endpoint.client` priority values using the following rule: the higher the priority the lower the value (non-specified or `0` values are interpreted as the highest priority -- `1`). -2. Deprecated `profiler` and `metrics` configuration sections are dropped, + +Deprecated `profiler` and `metrics` configuration sections are dropped, use `pprof` and `prometheus` instead. ## [0.30.2] - 2022-08-01 @@ -513,12 +536,13 @@ use `pprof` and `prometheus` instead. - `github.com/spf13/cobra` to v1.5.0 ### Updating from v0.29.0 -1. Change morph endpoints from simple string to a pair of `address` and `priority`. The second can be omitted. +Change morph endpoints from simple string to a pair of `address` and `priority`. The second can be omitted. For inner ring node this resides in `morph.endpoint.client` section, for storage node -- in `morph.rpc_endpoint` section. See `config/example` for an example. -2. Move `storage.default` section to `storage.shard.default`. -3. Rename `metrics` and `profiler` sections to `prometheus` and `pprof` respectively, though old versions are supported. +Move `storage.default` section to `storage.shard.default`. + +Rename `metrics` and `profiler` sections to `prometheus` and `pprof` respectively, though old versions are supported. In addition, these sections must now be explicitly enabled with `enabled: true` flag. ## [0.29.0] - 2022-07-07 - Yeonpyeongdo (연평도, 延坪島) @@ -767,7 +791,7 @@ See example config for more details. - `--generate-key` flag in CLI control commands (#1103) - Various unused code (#1123) -### Upgrading from v0.27.4 +### Updating from v0.27.4 Use `--wallet` key in CLI to provide WIF or binary key file instead of `--wif` and `--binary-key`. @@ -793,7 +817,7 @@ Specify `password: xxx` in config file for NeoFS CLI to avoid password input. - Factor out autocomplete command in CLI and Adm (#1041) - Single crypto rand source (#851) -### Upgrading from v0.27.3 +### Updating from v0.27.3 To disable compression for object with specific content-types, specify them as a string array in blobstor section: `NEOFS_STORAGE_SHARD_N_BLOBSTOR_COMPRESSION_EXCLUDE_CONTENT_TYPES`. Use @@ -874,7 +898,7 @@ NeoFS API v2.11.0 support with response status codes and storage subnetworks. - Alphabet nodes now invoke `netmap.Register` to add node to the network map candidates in notary enabled environment (#1008) -### Upgrading from v0.26.1 +### Updating from v0.26.1 `NEOFS_IR_CONTRACTS_ALPHABET_AMOUNT` is not mandatory env anymore. If it is not set, Inner Ring would try to read maximum from config and NNS contract. However, that parameter still can be set in order to require the exact number @@ -923,7 +947,7 @@ with `NEOFS_IR_FEE_NAMED_CONTAINER_REGISTER`. - LOCODE generator tries to find the closest continent if there are no exact match (#955) -### Upgrading from v0.26.0 +### Updating from v0.26.0 You can specify default section in storage engine configuration. See [example](./config/example/node.yaml) for more details. @@ -972,11 +996,12 @@ NeoFS API v2.10 support command (#810). - Interactive mode in docker run command (#916) -### Upgrading from v0.25.1 +### Updating from v0.25.1 Deleted `NEOFS_IR_NOTARY_SIDE_DEPOSIT_AMOUNT`, `NEOFS_IR_NOTARY_MAIN_DEPOSIT_AMOUNT` and `NEOFS_IR_TIMERS_SIDE_NOTARY`, `NEOFS_IR_TIMERS_MAIN_NOTARY` Inner Ring envs. Deleted `NEOFS_MORPH_NOTARY_DEPOSIT_AMOUNT` and `NEOFS_MORPH_NOTARY_DEPOSIT_DURATION` Storage Node envs. + `control` CLI command does not have `--rpc-endpoint`/`r` flag, use `endpoint` instead. @@ -1031,7 +1056,7 @@ instead. ### Removed - Dockerfile for AllInOne image moved to a separate repository (#796) -### Upgrading from v0.24.1 +### Updating from v0.24.1 Added `NEOFS_CONTRACTS_PROXY` env for Storage Node; mandatory in notary enabled environments only. It should contain proxy contract's scripthash in side chain. @@ -1061,7 +1086,7 @@ Object service pool size now split into `NEOFS_OBJECT_PUT_POOL_SIZE_REMOTE` and - Storage and Inner Ring will not start until Neo RPC node will have the height of the latest processed block by the nodes (#795) -### Upgrading from v0.24.0 +### Updating from v0.24.0 Specify path to the local state DB in Inner Ring node config with `NEOFS_IR_NODE_PERSISTENT_STATE_PATH`. Specify path to the local state DB in Storage node config with `NEOFS_NODE_PERSISTENT_STATE_PATH`. @@ -1093,7 +1118,7 @@ Storage node config with `NEOFS_NODE_PERSISTENT_STATE_PATH`. ### Removed - Unused `DB_SIZE` parameter of writecache (#773) -### Upgrading from v0.23.1 +### Updating from v0.23.1 Storage Node does not read unused `NEOFS_STORAGE_SHARD_XXX_WRITECACHE_DB_SIZE` config parameter anymore. @@ -1121,7 +1146,7 @@ N3 Mainnet launch release with minor fixes. - Handle context shutdown at NeoFS multi client group address switching (#737) - Scope for main chain invocations from Inner Ring nodes (#751) -### Upgrading from v0.23.0 +### Updating from v0.23.0 Added `NEOFS_MORPH_DISABLE_CACHE` env. If `true`, none of the `eACL`/`netmap`/`container` RPC responses cached. @@ -1149,7 +1174,7 @@ Improved stability for notary disabled environment. - Inner Ring node does not require proxy and processing contracts if notary disabled (#701, #714) -### Upgrading from v0.22.3 +### Updating from v0.22.3 To upgrade Storage node or Inner Ring node from v0.22.3, you don't need to change configuration files. Make sure, that NEO RPC nodes, specified in config, are connected to N3 RC4 (Testnet) network. diff --git a/cmd/neofs-adm/internal/modules/morph/dump_hashes.go b/cmd/neofs-adm/internal/modules/morph/dump_hashes.go index 3378d8a967..139cd7c950 100644 --- a/cmd/neofs-adm/internal/modules/morph/dump_hashes.go +++ b/cmd/neofs-adm/internal/modules/morph/dump_hashes.go @@ -269,20 +269,29 @@ func fillContractExpiration(cmd *cobra.Command, c Client, infos []contractDumpIn if err != nil { continue // OK for NNS itself, for example. } - elems := props.Value().([]stackitem.MapElement) - for _, e := range elems { - k, err := e.Key.TryBytes() - if err != nil { - continue - } + exp, err := expirationFromProperties(props) + if err != nil { + continue // Should be there, but who knows. + } + infos[i].expiration = exp + } +} + +func expirationFromProperties(props *stackitem.Map) (int64, error) { + elems := props.Value().([]stackitem.MapElement) + for _, e := range elems { + k, err := e.Key.TryBytes() + if err != nil { + continue + } - if string(k) == "expiration" { - v, err := e.Value.TryInteger() - if err != nil || !v.IsInt64() { - continue - } - infos[i].expiration = v.Int64() + if string(k) == "expiration" { + v, err := e.Value.TryInteger() + if err != nil || !v.IsInt64() { + continue } + return v.Int64(), nil } } + return 0, errors.New("not found") } diff --git a/cmd/neofs-adm/internal/modules/morph/dump_names.go b/cmd/neofs-adm/internal/modules/morph/dump_names.go new file mode 100644 index 0000000000..00b07b6267 --- /dev/null +++ b/cmd/neofs-adm/internal/modules/morph/dump_names.go @@ -0,0 +1,90 @@ +package morph + +import ( + "bytes" + "fmt" + "sort" + "strings" + "text/tabwriter" + "time" + + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const ( + nameDomainFlag = "domain" +) + +type nameExp struct { + name string + exp int64 +} + +func dumpNames(cmd *cobra.Command, _ []string) error { + c, err := getN3Client(viper.GetViper()) + if err != nil { + return fmt.Errorf("can't create N3 client: %w", err) + } + cs, err := c.GetContractStateByID(1) // NNS. + if err != nil { + return err + } + var n11r = nep11.NewNonDivisibleReader(invoker.New(c, nil), cs.Hash) + tokIter, err := n11r.Tokens() + if err != nil { + return err + } + zone, _ := cmd.Flags().GetString(nameDomainFlag) + var res = make([]nameExp, 0) + for toks, err := tokIter.Next(10); len(toks) != 0 && err == nil; toks, err = tokIter.Next(10) { + for i := range toks { + var name = string(toks[i]) + if zone != "" && name != zone && !strings.HasSuffix(name, "."+zone) { + continue + } + props, err := n11r.Properties(toks[i]) + if err != nil { + cmd.PrintErrf("Error getting properties for %s: %v\n", name, err) + continue + } + exp, err := expirationFromProperties(props) + if err != nil { + cmd.PrintErrf("Error getting expiration from properties for %s: %v\n", name, err) + continue + } + res = append(res, nameExp{name: name, exp: exp}) + } + } + + sort.Slice(res, func(i, j int) bool { + var ( + iParts = strings.Split(res[i].name, ".") + jParts = strings.Split(res[j].name, ".") + ) + if len(iParts) != len(jParts) { + return len(iParts) < len(jParts) + } + for k := len(iParts) - 1; k >= 0; k-- { + var c = strings.Compare(iParts[k], jParts[k]) + if c != 0 { + return c == -1 + } + } + return false + }) + + buf := bytes.NewBuffer(nil) + tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0) + for i := range res { + _, _ = tw.Write([]byte(fmt.Sprintf("%s\t%s\n", + res[i].name, time.UnixMilli(res[i].exp).String()))) + } + _ = tw.Flush() + + cmd.Print(buf.String()) + + return nil +} diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go index ee0b465194..edf68620da 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go @@ -47,7 +47,6 @@ const ( netmapContract = "netmap" proxyContract = "proxy" reputationContract = "reputation" - subnetContract = "subnet" ) const ( @@ -77,7 +76,6 @@ var ( netmapContract, proxyContract, reputationContract, - subnetContract, } fullContractList = append([]string{ @@ -597,7 +595,6 @@ func (c *initializeContext) getContractDeployData(ctrHash util.Uint160, ctrName case proxyContract: items = nil case reputationContract: - case subnetContract: default: panic(fmt.Sprintf("invalid contract name: %s", ctrName)) } diff --git a/cmd/neofs-adm/internal/modules/morph/internal/types.go b/cmd/neofs-adm/internal/modules/morph/internal/types.go deleted file mode 100644 index 705ae00746..0000000000 --- a/cmd/neofs-adm/internal/modules/morph/internal/types.go +++ /dev/null @@ -1,65 +0,0 @@ -package internal - -import ( - "fmt" - "strconv" - - "google.golang.org/protobuf/proto" -) - -// StringifySubnetClientGroupID returns string representation of SubnetClientGroupID using MarshalText. -// Returns a string with a message on error. -func StringifySubnetClientGroupID(id *SubnetClientGroupID) string { - text, err := id.MarshalText() - if err != nil { - return fmt.Sprintf(" %v", err) - } - - return string(text) -} - -// MarshalText encodes SubnetClientGroupID into text format according to NeoFS API V2 protocol: -// value in base-10 integer string format. -// -// It implements encoding.TextMarshaler. -func (x *SubnetClientGroupID) MarshalText() ([]byte, error) { - num := x.GetValue() // NPE safe, returns zero on nil - - return []byte(strconv.FormatUint(uint64(num), 10)), nil -} - -// UnmarshalText decodes the SubnetID from the text according to NeoFS API V2 protocol: -// should be base-10 integer string format with bitsize = 32. -// -// Returns strconv.ErrRange if integer overflows uint32. -// -// Must not be called on nil. -// -// Implements encoding.TextUnmarshaler. -func (x *SubnetClientGroupID) UnmarshalText(txt []byte) error { - num, err := strconv.ParseUint(string(txt), 10, 32) - if err != nil { - return fmt.Errorf("invalid numeric value: %w", err) - } - - x.SetNumber(uint32(num)) - - return nil -} - -// Marshal encodes the SubnetClientGroupID into a binary format of NeoFS API V2 protocol -// (Protocol Buffers with direct field order). -func (x *SubnetClientGroupID) Marshal() ([]byte, error) { - return proto.Marshal(x) -} - -// Unmarshal decodes the SubnetClientGroupID from NeoFS API V2 binary format (see Marshal). Must not be called on nil. -func (x *SubnetClientGroupID) Unmarshal(data []byte) error { - return proto.Unmarshal(data, x) -} - -// SetNumber sets SubnetClientGroupID value in uint32 format. Must not be called on nil. -// By default, number is 0. -func (x *SubnetClientGroupID) SetNumber(num uint32) { - x.Value = num -} diff --git a/cmd/neofs-adm/internal/modules/morph/internal/types.pb.go b/cmd/neofs-adm/internal/modules/morph/internal/types.pb.go deleted file mode 100644 index 6d43c96aa3..0000000000 --- a/cmd/neofs-adm/internal/modules/morph/internal/types.pb.go +++ /dev/null @@ -1,156 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.26.0 -// protoc v3.21.7 -// source: cmd/neofs-adm/internal/modules/morph/internal/types.proto - -package internal - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// Client group identifier in the NeoFS subnet. -// -// String representation of a value is base-10 integer. -// -// JSON representation is an object containing single `value` number field. -type SubnetClientGroupID struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // 4-byte integer identifier of the subnet client group. - Value uint32 `protobuf:"fixed32,1,opt,name=value,proto3" json:"value,omitempty"` -} - -func (x *SubnetClientGroupID) Reset() { - *x = SubnetClientGroupID{} - if protoimpl.UnsafeEnabled { - mi := &file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SubnetClientGroupID) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubnetClientGroupID) ProtoMessage() {} - -func (x *SubnetClientGroupID) ProtoReflect() protoreflect.Message { - mi := &file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubnetClientGroupID.ProtoReflect.Descriptor instead. -func (*SubnetClientGroupID) Descriptor() ([]byte, []int) { - return file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescGZIP(), []int{0} -} - -func (x *SubnetClientGroupID) GetValue() uint32 { - if x != nil { - return x.Value - } - return 0 -} - -var File_cmd_neofs_adm_internal_modules_morph_internal_types_proto protoreflect.FileDescriptor - -var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc = []byte{ - 0x0a, 0x39, 0x63, 0x6d, 0x64, 0x2f, 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x61, 0x64, 0x6d, 0x2f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, - 0x2f, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x6e, 0x65, 0x6f, - 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x72, 0x65, 0x66, 0x73, 0x22, 0x2b, 0x0a, 0x13, 0x53, - 0x75, 0x62, 0x6e, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x07, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x4f, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x73, 0x70, 0x63, 0x63, 0x2d, 0x64, 0x65, 0x76, - 0x2f, 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x63, 0x6d, 0x64, 0x2f, - 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x61, 0x64, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2f, 0x6d, 0x6f, 0x72, 0x70, 0x68, - 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, -} - -var ( - file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescOnce sync.Once - file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData = file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc -) - -func file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescGZIP() []byte { - file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescOnce.Do(func() { - file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData) - }) - return file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData -} - -var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_goTypes = []interface{}{ - (*SubnetClientGroupID)(nil), // 0: neo.fs.v2.refs.SubnetClientGroupID -} -var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_init() } -func file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_init() { - if File_cmd_neofs_adm_internal_modules_morph_internal_types_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubnetClientGroupID); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_goTypes, - DependencyIndexes: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_depIdxs, - MessageInfos: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes, - }.Build() - File_cmd_neofs_adm_internal_modules_morph_internal_types_proto = out.File - file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc = nil - file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_goTypes = nil - file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_depIdxs = nil -} diff --git a/cmd/neofs-adm/internal/modules/morph/internal/types.proto b/cmd/neofs-adm/internal/modules/morph/internal/types.proto deleted file mode 100644 index c610eb7aa4..0000000000 --- a/cmd/neofs-adm/internal/modules/morph/internal/types.proto +++ /dev/null @@ -1,15 +0,0 @@ -syntax = "proto3"; - -package neo.fs.v2.refs; - -option go_package = "github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal"; - -// Client group identifier in the NeoFS subnet. -// -// String representation of a value is base-10 integer. -// -// JSON representation is an object containing single `value` number field. -message SubnetClientGroupID { - // 4-byte integer identifier of the subnet client group. - fixed32 value = 1 [json_name = "value"]; -} diff --git a/cmd/neofs-adm/internal/modules/morph/renew_domain.go b/cmd/neofs-adm/internal/modules/morph/renew_domain.go new file mode 100644 index 0000000000..dfbb073ad1 --- /dev/null +++ b/cmd/neofs-adm/internal/modules/morph/renew_domain.go @@ -0,0 +1,73 @@ +package morph + +import ( + "errors" + "strings" + + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const ( + recursiveFlag = "recursive" +) + +func renewDomain(cmd *cobra.Command, _ []string) error { + dom, err := cmd.Flags().GetString(nameDomainFlag) + if err != nil { + return err + } + recursive, _ := cmd.Flags().GetBool(recursiveFlag) + wCtx, err := newInitializeContext(cmd, viper.GetViper()) + if err != nil { + return err + } + defer wCtx.close() + nns, err := wCtx.Client.GetContractStateByID(1) + if err != nil { + return err + } + var domains = make([]string, 0, 1) + if recursive { + var n11r = nep11.NewNonDivisibleReader(wCtx.ReadOnlyInvoker, nns.Hash) + tokIter, err := n11r.Tokens() + if err != nil { + return err + } + for toks, err := tokIter.Next(10); len(toks) != 0 && err == nil; toks, err = tokIter.Next(10) { + for i := range toks { + var name = string(toks[i]) + if name != dom && !strings.HasSuffix(name, "."+dom) { + continue + } + domains = append(domains, name) + } + } + } else { + avail, err := unwrap.Bool(wCtx.ReadOnlyInvoker.Call(nns.Hash, "isAvailable")) + if err == nil && avail { + return errors.New("domain is not registered or expired") + } + domains = append(domains, dom) + } + + bw := io.NewBufBinWriter() + for i := range domains { + emit.AppCall(bw.BinWriter, nns.Hash, "renew", callflag.All, domains[i]) + if bw.Err != nil { + return bw.Err + } + // Default registration price is 10 GAS, adding more domains + // into the script makes test execution to fail. + if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil { + return err + } + bw.Reset() + } + return wCtx.awaitTx() +} diff --git a/cmd/neofs-adm/internal/modules/morph/root.go b/cmd/neofs-adm/internal/modules/morph/root.go index a33a99ef76..bbbc8f71ad 100644 --- a/cmd/neofs-adm/internal/modules/morph/root.go +++ b/cmd/neofs-adm/internal/modules/morph/root.go @@ -162,6 +162,15 @@ var ( RunE: dumpContractHashes, } + dumpNamesCmd = &cobra.Command{ + Use: "dump-names", + Short: "Dump known registred NNS names and expirations", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + RunE: dumpNames, + } + dumpNetworkConfigCmd = &cobra.Command{ Use: "dump-config", Short: "Dump NeoFS network config", @@ -199,6 +208,16 @@ var ( RunE: dumpContainers, } + renewDomainCmd = &cobra.Command{ + Use: "renew-domain", + Short: "Renew NNS domain", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + RunE: renewDomain, + } + restoreContainersCmd = &cobra.Command{ Use: "restore-containers", Short: "Restore NeoFS containers from file", @@ -271,6 +290,10 @@ func init() { dumpContractHashesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.") + RootCmd.AddCommand(dumpNamesCmd) + dumpNamesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + dumpNamesCmd.Flags().StringP(nameDomainFlag, "d", "", "Filter by domain") + RootCmd.AddCommand(dumpNetworkConfigCmd) dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") @@ -297,6 +320,12 @@ func init() { dumpContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)") dumpContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to dump") + RootCmd.AddCommand(renewDomainCmd) + renewDomainCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + renewDomainCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + renewDomainCmd.Flags().StringP(nameDomainFlag, "d", "", "Domain") + renewDomainCmd.Flags().BoolP(recursiveFlag, "u", false, "Recursive (renew all subdomain as well)") + RootCmd.AddCommand(restoreContainersCmd) restoreContainersCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") restoreContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") @@ -315,8 +344,6 @@ func init() { refillGasCmd.Flags().String(refillGasAmountFlag, "", "Additional amount of GAS to transfer") refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, storageWalletFlag) - RootCmd.AddCommand(cmdSubnet) - RootCmd.AddCommand(depositNotaryCmd) depositNotaryCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") depositNotaryCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet") diff --git a/cmd/neofs-adm/internal/modules/morph/subnet.go b/cmd/neofs-adm/internal/modules/morph/subnet.go deleted file mode 100644 index 270abc710b..0000000000 --- a/cmd/neofs-adm/internal/modules/morph/subnet.go +++ /dev/null @@ -1,1068 +0,0 @@ -package morph - -import ( - "encoding/hex" - "errors" - "fmt" - - "github.com/nspcc-dev/neo-go/cli/flags" - "github.com/nspcc-dev/neo-go/cli/input" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" - "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/neorpc/result" - "github.com/nspcc-dev/neo-go/pkg/rpcclient" - "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/opcode" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/nspcc-dev/neo-go/pkg/wallet" - "github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal" - "github.com/nspcc-dev/neofs-node/pkg/morph/client" - "github.com/nspcc-dev/neofs-node/pkg/util/rand" - neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" - "github.com/nspcc-dev/neofs-sdk-go/subnet" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" - "github.com/nspcc-dev/neofs-sdk-go/user" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func viperBindFlags(cmd *cobra.Command, flags ...string) { - for i := range flags { - _ = viper.BindPFlag(flags[i], cmd.Flags().Lookup(flags[i])) - } -} - -// subnet command section. -var cmdSubnet = &cobra.Command{ - Use: "subnet", - Short: "NeoFS subnet management", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - endpointFlag, - ) - }, -} - -// shared flags of cmdSubnet sub-commands. -const ( - flagSubnet = "subnet" // subnet identifier - flagSubnetGroup = "group" // subnet client group ID - flagSubnetWallet = "wallet" // filepath to wallet - flagSubnetAddress = "address" // address in the wallet, optional -) - -// reads wallet from the filepath configured in flagSubnetWallet flag, -// looks for address specified in flagSubnetAddress flag (uses default -// address if flag is empty) and decrypts private key. -func readSubnetKey(key *keys.PrivateKey) error { - // read wallet from file - - walletPath := viper.GetString(flagSubnetWallet) - if walletPath == "" { - return errors.New("missing path to wallet") - } - - w, err := wallet.NewWalletFromFile(walletPath) - if err != nil { - return fmt.Errorf("read wallet from file: %w", err) - } - - // read account from the wallet - - var ( - addr util.Uint160 - addrStr = viper.GetString(flagSubnetAddress) - ) - - if addrStr == "" { - addr = w.GetChangeAddress() - } else { - addr, err = flags.ParseAddress(addrStr) - if err != nil { - return fmt.Errorf("read wallet address: %w", err) - } - } - - acc := w.GetAccount(addr) - if acc == nil { - return fmt.Errorf("address %s not found in %s", addrStr, walletPath) - } - - // read password - pass, err := input.ReadPassword("Enter password > ") - if err != nil { - return fmt.Errorf("read password: %w", err) - } - - // decrypt with just read password - err = acc.Decrypt(pass, keys.NEP2ScryptParams()) - if err != nil { - return fmt.Errorf("decrypt wallet: %w", err) - } - - *key = *acc.PrivateKey() - - return nil -} - -// create subnet command. -var cmdSubnetCreate = &cobra.Command{ - Use: "create", - Short: "Create NeoFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetAddress, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // generate subnet ID and marshal it - var ( - id subnetid.ID - num uint32 - ) - - for { - num = rand.Uint32() - - id.SetNumeric(num) - - if !subnetid.IsZero(id) { - break - } - } - - // declare creator ID and encode it - var creator user.ID - err = user.IDFromSigner(&creator, neofsecdsa.SignerRFC6979(key.PrivateKey)) - if err != nil { - return fmt.Errorf("decoding user from key: %w", err) - } - - // fill subnet info and encode it - var info subnet.Info - - info.SetID(id) - info.SetOwner(creator) - - err = invokeMethod(key, true, "put", id.Marshal(), key.PublicKey().Bytes(), info.Marshal()) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - cmd.Printf("Create subnet request sent successfully. ID: %s.\n", &id) - - return nil - }, -} - -// cmdSubnetRemove flags. -const ( - // subnet ID to be removed. - flagSubnetRemoveID = flagSubnet -) - -// errZeroSubnet is returned on attempts to work with zero subnet which is virtual. -var errZeroSubnet = errors.New("zero subnet") - -// remove subnet command. -var cmdSubnetRemove = &cobra.Command{ - Use: "remove", - Short: "Remove NeoFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetAddress, - flagSubnetRemoveID, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // read ID and encode it - var id subnetid.ID - - err = id.DecodeString(viper.GetString(flagSubnetRemoveID)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - err = invokeMethod(key, false, "delete", id.Marshal()) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - cmd.Println("Remove subnet request sent successfully") - - return nil - }, -} - -// cmdSubnetGet flags. -const ( - // subnet ID to be read. - flagSubnetGetID = flagSubnet -) - -// get subnet command. -var cmdSubnetGet = &cobra.Command{ - Use: "get", - Short: "Read information about the NeoFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetGetID, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - // read ID and encode it - var id subnetid.ID - - err := id.DecodeString(viper.GetString(flagSubnetGetID)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - // use random key to fetch the data - // we could use raw neo-go client to perform testInvoke - // without keys, as it is done in other commands - key, err := keys.NewPrivateKey() - if err != nil { - return fmt.Errorf("init subnet client: %w", err) - } - - res, err := testInvokeMethod(*key, "get", id.Marshal()) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - if len(res) == 0 { - return errors.New("subnet does not exist") - } - - data, err := client.BytesFromStackItem(res[0]) - if err != nil { - return fmt.Errorf("decoding contract response: %w", err) - } - - // decode info - var info subnet.Info - if err = info.Unmarshal(data); err != nil { - return fmt.Errorf("decode subnet info: %w", err) - } - - // print information - cmd.Printf("Owner: %s\n", info.Owner()) - - return nil - }, -} - -// cmdSubnetAdmin subnet flags. -const ( - flagSubnetAdminSubnet = flagSubnet // subnet ID to be managed - flagSubnetAdminID = "admin" // admin public key - flagSubnetAdminClient = "client" // manage client admins instead of node ones -) - -// command to manage subnet admins. -var cmdSubnetAdmin = &cobra.Command{ - Use: "admin", - Short: "Manage administrators of the NeoFS subnet", - PreRun: func(cmd *cobra.Command, args []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetAddress, - flagSubnetAdminSubnet, - flagSubnetAdminID, - ) - }, -} - -// cmdSubnetAdminAdd flags. -const ( - flagSubnetAdminAddGroup = flagSubnetGroup // client group ID -) - -// common executor cmdSubnetAdminAdd and cmdSubnetAdminRemove commands. -func manageSubnetAdmins(cmd *cobra.Command, rm bool) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // read ID and encode it - var id subnetid.ID - - err = id.DecodeString(viper.GetString(flagSubnetAdminSubnet)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - // read admin key and decode it - binAdminKey, err := hex.DecodeString(viper.GetString(flagSubnetAdminID)) - if err != nil { - return fmt.Errorf("decode admin key text: %w", err) - } - - var pubkey keys.PublicKey - if err = pubkey.DecodeBytes(binAdminKey); err != nil { - return fmt.Errorf("admin key format: %w", err) - } - - // prepare call parameters - prm := make([]interface{}, 0, 3) - prm = append(prm, id.Marshal()) - - var method string - - if viper.GetBool(flagSubnetAdminClient) { - // read group ID and encode it - var groupID internal.SubnetClientGroupID - - err = groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup))) - if err != nil { - return fmt.Errorf("decode group ID text: %w", err) - } - - binGroupID, err := groupID.Marshal() - if err != nil { - return fmt.Errorf("marshal group ID: %w", err) - } - - if rm { - method = "removeClientAdmin" - } else { - method = "addClientAdmin" - } - - prm = append(prm, binGroupID) - } else { - if rm { - method = "removeNodeAdmin" - } else { - method = "addNodeAdmin" - } - } - - prm = append(prm, binAdminKey) - - err = invokeMethod(key, false, method, prm...) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - var op string - - if rm { - op = "Remove" - } else { - op = "Add" - } - - cmd.Printf("%s admin request sent successfully.\n", op) - - return nil -} - -// command to add subnet admin. -var cmdSubnetAdminAdd = &cobra.Command{ - Use: "add", - Short: "Add admin to the NeoFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetAdminAddGroup, - flagSubnetAdminClient, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetAdmins(cmd, false) - }, -} - -// command to remove subnet admin. -var cmdSubnetAdminRemove = &cobra.Command{ - Use: "remove", - Short: "Remove admin of the NeoFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetAdminClient, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetAdmins(cmd, true) - }, -} - -// cmdSubnetClient flags. -const ( - flagSubnetClientSubnet = flagSubnet // ID of the subnet to be managed - flagSubnetClientID = flagSubnetAdminClient // client's NeoFS ID - flagSubnetClientGroup = flagSubnetGroup // ID of the subnet client group -) - -// command to manage subnet clients. -var cmdSubnetClient = &cobra.Command{ - Use: "client", - Short: "Manage clients of the NeoFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetAddress, - flagSubnetClientSubnet, - flagSubnetClientID, - flagSubnetClientGroup, - ) - }, -} - -// common executor cmdSubnetClientAdd and cmdSubnetClientRemove commands. -func manageSubnetClients(cmd *cobra.Command, rm bool) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // read ID and encode it - var id subnetid.ID - - err = id.DecodeString(viper.GetString(flagSubnetClientSubnet)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - // read client ID and encode it - var clientID user.ID - - err = clientID.DecodeString(viper.GetString(flagSubnetClientID)) - if err != nil { - return fmt.Errorf("decode client ID text: %w", err) - } - - // read group ID and encode it - var groupID internal.SubnetClientGroupID - - err = groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup))) - if err != nil { - return fmt.Errorf("decode group ID text: %w", err) - } - - binGroupID, err := groupID.Marshal() - if err != nil { - return fmt.Errorf("marshal group ID: %w", err) - } - - var method string - if rm { - method = "removeUser" - } else { - method = "addUser" - } - - err = invokeMethod(key, false, method, id.Marshal(), binGroupID, clientID.WalletBytes()) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - var op string - - if rm { - op = "Remove" - } else { - op = "Add" - } - - cmd.Printf("%s client request sent successfully.\n", op) - - return nil -} - -// command to add subnet client. -var cmdSubnetClientAdd = &cobra.Command{ - Use: "add", - Short: "Add client to the NeoFS subnet", - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetClients(cmd, false) - }, -} - -// command to remove subnet client. -var cmdSubnetClientRemove = &cobra.Command{ - Use: "remove", - Short: "Remove client of the NeoFS subnet", - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetClients(cmd, true) - }, -} - -// cmdSubnetNode flags. -const ( - flagSubnetNode = "node" // node ID - flagSubnetNodeSubnet = flagSubnet // ID of the subnet to be managed -) - -// common executor cmdSubnetNodeAdd and cmdSubnetNodeRemove commands. -func manageSubnetNodes(cmd *cobra.Command, rm bool) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // read ID and encode it - var id subnetid.ID - - err = id.DecodeString(viper.GetString(flagSubnetNodeSubnet)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - // read node ID and encode it - binNodeID, err := hex.DecodeString(viper.GetString(flagSubnetNode)) - if err != nil { - return fmt.Errorf("decode node ID text: %w", err) - } - - var pubkey keys.PublicKey - if err = pubkey.DecodeBytes(binNodeID); err != nil { - return fmt.Errorf("node ID format: %w", err) - } - - var method string - if rm { - method = "removeNode" - } else { - method = "addNode" - } - - err = invokeMethod(key, false, method, id.Marshal(), binNodeID) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - var op string - - if rm { - op = "Remove" - } else { - op = "Add" - } - - cmd.Printf("%s node request sent successfully.\n", op) - - return nil -} - -// command to manage subnet nodes. -var cmdSubnetNode = &cobra.Command{ - Use: "node", - Short: "Manage nodes of the NeoFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetNode, - flagSubnetNodeSubnet, - ) - }, -} - -// command to add subnet node. -var cmdSubnetNodeAdd = &cobra.Command{ - Use: "add", - Short: "Add node to the NeoFS subnet", - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetNodes(cmd, false) - }, -} - -// command to remove subnet node. -var cmdSubnetNodeRemove = &cobra.Command{ - Use: "remove", - Short: "Remove node from the NeoFS subnet", - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetNodes(cmd, true) - }, -} - -// returns function which calls PreRun on parent if it exists. -func inheritPreRun(preRun func(*cobra.Command, []string)) func(*cobra.Command, []string) { - return func(cmd *cobra.Command, args []string) { - par := cmd.Parent() - if par != nil && par.PreRun != nil { - par.PreRun(par, args) - } - - if preRun != nil { - preRun(cmd, args) - } - } -} - -// inherits PreRun function of parent command in all sub-commands and -// adds them to the parent. -func addCommandInheritPreRun(par *cobra.Command, subs ...*cobra.Command) { - for _, sub := range subs { - sub.PreRun = inheritPreRun(sub.PreRun) - } - - par.AddCommand(subs...) -} - -// registers flags and binds sub-commands for subnet commands. -func init() { - cmdSubnetCreate.Flags().StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetCreate.MarkFlagRequired(flagSubnetWallet) - cmdSubnetCreate.Flags().StringP(flagSubnetAddress, "a", "", "Address in the wallet, optional") - - // get subnet flags - cmdSubnetGet.Flags().String(flagSubnetGetID, "", "ID of the subnet to read") - _ = cmdSubnetAdminAdd.MarkFlagRequired(flagSubnetGetID) - - // remove subnet flags - cmdSubnetRemove.Flags().String(flagSubnetRemoveID, "", "ID of the subnet to remove") - _ = cmdSubnetRemove.MarkFlagRequired(flagSubnetRemoveID) - cmdSubnetRemove.Flags().StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetRemove.MarkFlagRequired(flagSubnetWallet) - cmdSubnetRemove.Flags().StringP(flagSubnetAddress, "a", "", "Address in the wallet, optional") - - // subnet administer flags - adminFlags := cmdSubnetAdmin.PersistentFlags() - adminFlags.String(flagSubnetAdminSubnet, "", "ID of the subnet to manage administrators") - _ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetAdminSubnet) - adminFlags.String(flagSubnetAdminID, "", "Hex-encoded public key of the admin") - _ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetAdminID) - adminFlags.StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetWallet) - adminFlags.StringP(flagSubnetAddress, "a", "", "Address in the wallet, optional") - - // add admin flags - cmdSubnetAdminAddFlags := cmdSubnetAdminAdd.Flags() - cmdSubnetAdminAddFlags.String(flagSubnetAdminAddGroup, "", fmt.Sprintf( - "Client group ID in text format (needed with --%s only)", flagSubnetAdminClient)) - cmdSubnetAdminAddFlags.Bool(flagSubnetAdminClient, false, "Add client admin instead of node one") - - // remove admin flags - cmdSubnetAdminRemoveFlags := cmdSubnetAdminRemove.Flags() - cmdSubnetAdminRemoveFlags.Bool(flagSubnetAdminClient, false, "Remove client admin instead of node one") - - // client managements flags - clientFlags := cmdSubnetClient.PersistentFlags() - clientFlags.String(flagSubnetClientSubnet, "", "ID of the subnet to be managed") - _ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientSubnet) - clientFlags.String(flagSubnetClientGroup, "", "ID of the client group to work with") - _ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientGroup) - clientFlags.String(flagSubnetClientID, "", "Client's user ID in NeoFS system in text format") - _ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientID) - clientFlags.StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetClient.MarkFlagRequired(flagSubnetWallet) - clientFlags.StringP(flagSubnetAddress, "a", "", "Address in the wallet, optional") - - // add all admin managing commands to corresponding command section - addCommandInheritPreRun(cmdSubnetAdmin, - cmdSubnetAdminAdd, - cmdSubnetAdminRemove, - ) - - // add all client managing commands to corresponding command section - addCommandInheritPreRun(cmdSubnetClient, - cmdSubnetClientAdd, - cmdSubnetClientRemove, - ) - - // subnet node flags - nodeFlags := cmdSubnetNode.PersistentFlags() - nodeFlags.StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetNode.MarkFlagRequired(flagSubnetWallet) - nodeFlags.String(flagSubnetNode, "", "Hex-encoded public key of the node") - _ = cmdSubnetNode.MarkFlagRequired(flagSubnetNode) - nodeFlags.String(flagSubnetNodeSubnet, "", "ID of the subnet to manage nodes") - _ = cmdSubnetNode.MarkFlagRequired(flagSubnetNodeSubnet) - - // add all node managing commands to corresponding command section - addCommandInheritPreRun(cmdSubnetNode, - cmdSubnetNodeAdd, - cmdSubnetNodeRemove, - ) - - // subnet global flags - cmdSubnetFlags := cmdSubnet.PersistentFlags() - cmdSubnetFlags.StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - _ = cmdSubnet.MarkFlagRequired(endpointFlag) - - // add all subnet commands to corresponding command section - addCommandInheritPreRun(cmdSubnet, - cmdSubnetCreate, - cmdSubnetRemove, - cmdSubnetGet, - cmdSubnetAdmin, - cmdSubnetClient, - cmdSubnetNode, - ) -} - -func testInvokeMethod(key keys.PrivateKey, method string, args ...interface{}) ([]stackitem.Item, error) { - c, err := getN3Client(viper.GetViper()) - if err != nil { - return nil, fmt.Errorf("morph client creation: %w", err) - } - - nnsCs, err := c.GetContractStateByID(1) - if err != nil { - return nil, fmt.Errorf("NNS contract resolving: %w", err) - } - - cosigner := []transaction.Signer{ - { - Account: key.PublicKey().GetScriptHash(), - Scopes: transaction.Global, - }, - } - - inv := invoker.New(c, cosigner) - - subnetHash, err := nnsResolveHash(inv, nnsCs.Hash, subnetContract+".neofs") - if err != nil { - return nil, fmt.Errorf("subnet hash resolving: %w", err) - } - - res, err := inv.Call(subnetHash, method, args...) - if err != nil { - return nil, fmt.Errorf("invocation parameters prepararion: %w", err) - } - - err = checkInvocationResults(res) - if err != nil { - return nil, err - } - - return res.Stack, nil -} - -func invokeMethod(key keys.PrivateKey, tryNotary bool, method string, args ...interface{}) error { - c, err := getN3Client(viper.GetViper()) - if err != nil { - return fmt.Errorf("morph client creation: %w", err) - } - - if tryNotary { - cc, err := c.GetNativeContracts() - if err != nil { - return fmt.Errorf("native hashes: %w", err) - } - - var notary bool - var notaryHash util.Uint160 - for _, c := range cc { - if c.Manifest.Name == nativenames.Notary { - notary = len(c.UpdateHistory) > 0 - notaryHash = c.Hash - - break - } - } - - if notary { - err = invokeNotary(c, key, method, notaryHash, args...) - if err != nil { - return fmt.Errorf("notary invocation: %w", err) - } - - return nil - } - } - - err = invokeNonNotary(c, key, method, args...) - if err != nil { - return fmt.Errorf("non-notary invocation: %w", err) - } - - return nil -} - -func invokeNonNotary(c Client, key keys.PrivateKey, method string, args ...interface{}) error { - nnsCs, err := c.GetContractStateByID(1) - if err != nil { - return fmt.Errorf("NNS contract resolving: %w", err) - } - - acc := wallet.NewAccountFromPrivateKey(&key) - - cosigner := []transaction.Signer{ - { - Account: key.PublicKey().GetScriptHash(), - Scopes: transaction.Global, - }, - } - - cosignerAcc := []rpcclient.SignerAccount{ - { - Signer: cosigner[0], - Account: acc, - }, - } - - inv := invoker.New(c, cosigner) - - subnetHash, err := nnsResolveHash(inv, nnsCs.Hash, subnetContract+".neofs") - if err != nil { - return fmt.Errorf("subnet hash resolving: %w", err) - } - - test, err := inv.Call(subnetHash, method, args...) - if err != nil { - return fmt.Errorf("test invocation: %w", err) - } - - err = checkInvocationResults(test) - if err != nil { - return err - } - - _, err = c.SignAndPushInvocationTx(test.Script, acc, test.GasConsumed, 0, cosignerAcc) - if err != nil { - return fmt.Errorf("sending transaction: %w", err) - } - - return nil -} - -func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util.Uint160, args ...interface{}) error { - nnsCs, err := c.GetContractStateByID(1) - if err != nil { - return fmt.Errorf("NNS contract resolving: %w", err) - } - - alphabet, err := c.GetCommittee() - if err != nil { - return fmt.Errorf("alphabet list: %w", err) - } - - multisigScript, err := smartcontract.CreateDefaultMultiSigRedeemScript(alphabet) - if err != nil { - return fmt.Errorf("alphabet multi-signature script: %w", err) - } - - cosigners, err := notaryCosigners(c, notaryHash, nnsCs, key, hash.Hash160(multisigScript)) - if err != nil { - return fmt.Errorf("cosigners collecting: %w", err) - } - - inv := invoker.New(c, cosigners) - - subnetHash, err := nnsResolveHash(inv, nnsCs.Hash, subnetContract+".neofs") - if err != nil { - return fmt.Errorf("subnet hash resolving: %w", err) - } - - // make test invocation of the method - test, err := inv.Call(subnetHash, method, args...) - if err != nil { - return fmt.Errorf("test invocation: %w", err) - } - - err = checkInvocationResults(test) - if err != nil { - return err - } - - multisigAccount := &wallet.Account{ - Contract: &wallet.Contract{ - Script: multisigScript, - }, - } - - bc, err := c.GetBlockCount() - if err != nil { - return fmt.Errorf("blockchain height: %w", err) - } - - signersNumber := uint8(smartcontract.GetDefaultHonestNodeCount(len(alphabet)) + 1) // alphabet multisig + key signature - - // notaryRequestValidity is number of blocks during - // witch notary request is considered valid - const notaryRequestValidity = 100 - - mainTx := &transaction.Transaction{ - Nonce: rand.Uint32(), - SystemFee: test.GasConsumed, - ValidUntilBlock: bc + notaryRequestValidity, - Script: test.Script, - Attributes: []transaction.Attribute{ - { - Type: transaction.NotaryAssistedT, - Value: &transaction.NotaryAssisted{NKeys: signersNumber}, - }, - }, - Signers: cosigners, - } - - notaryFee, err := c.CalculateNotaryFee(signersNumber) - if err != nil { - return err - } - - acc := wallet.NewAccountFromPrivateKey(&key) - aa := notaryAccounts(multisigAccount, acc) - - err = c.AddNetworkFee(mainTx, notaryFee, aa...) - if err != nil { - return fmt.Errorf("notary network fee adding: %w", err) - } - - mainTx.Scripts = notaryWitnesses(c, multisigAccount, acc, mainTx) - - _, err = c.SignAndPushP2PNotaryRequest(mainTx, - []byte{byte(opcode.RET)}, - -1, - 0, - 40, - acc) - if err != nil { - return fmt.Errorf("sending notary request: %w", err) - } - - return nil -} - -func notaryCosigners(c Client, notaryHash util.Uint160, nnsCs *state.Contract, - key keys.PrivateKey, alphabetAccount util.Uint160) ([]transaction.Signer, error) { - proxyHash, err := nnsResolveHash(invoker.New(c, nil), nnsCs.Hash, proxyContract+".neofs") - if err != nil { - return nil, fmt.Errorf("proxy hash resolving: %w", err) - } - - return []transaction.Signer{ - { - Account: proxyHash, - Scopes: transaction.None, - }, - { - Account: alphabetAccount, - Scopes: transaction.Global, - }, - { - Account: hash.Hash160(key.PublicKey().GetVerificationScript()), - Scopes: transaction.Global, - }, - { - Account: notaryHash, - Scopes: transaction.None, - }, - }, nil -} - -func notaryAccounts(alphabet, acc *wallet.Account) []*wallet.Account { - return []*wallet.Account{ - // proxy - { - Contract: &wallet.Contract{ - Deployed: true, - }, - }, - alphabet, - // caller's account - acc, - // last one is a placeholder for notary contract account - { - Contract: &wallet.Contract{}, - }, - } -} - -func notaryWitnesses(c Client, alphabet, acc *wallet.Account, tx *transaction.Transaction) []transaction.Witness { - ww := make([]transaction.Witness, 0, 4) - - // empty proxy contract witness - ww = append(ww, transaction.Witness{ - InvocationScript: []byte{}, - VerificationScript: []byte{}, - }) - - // alphabet multi-address witness - ww = append(ww, transaction.Witness{ - InvocationScript: append( - []byte{byte(opcode.PUSHDATA1), 64}, - make([]byte, 64)..., - ), - VerificationScript: alphabet.GetVerificationScript(), - }) - - magicNumber, _ := c.GetNetwork() - - // caller's witness - ww = append(ww, transaction.Witness{ - InvocationScript: append( - []byte{byte(opcode.PUSHDATA1), 64}, - acc.SignHashable(magicNumber, tx)...), - VerificationScript: acc.GetVerificationScript(), - }) - - // notary contract witness - ww = append(ww, transaction.Witness{ - InvocationScript: append( - []byte{byte(opcode.PUSHDATA1), 64}, - make([]byte, 64)..., - ), - VerificationScript: []byte{}, - }) - - return ww -} - -func checkInvocationResults(res *result.Invoke) error { - if res.State != "HALT" { - return fmt.Errorf("test invocation state: %s, exception %s: ", res.State, res.FaultException) - } - - if len(res.Script) == 0 { - return errors.New("empty invocation script") - } - - return nil -} diff --git a/cmd/neofs-adm/internal/modules/storagecfg/config.go b/cmd/neofs-adm/internal/modules/storagecfg/config.go index 1efe0751b0..936a0d760e 100644 --- a/cmd/neofs-adm/internal/modules/storagecfg/config.go +++ b/cmd/neofs-adm/internal/modules/storagecfg/config.go @@ -12,9 +12,6 @@ node: - {{ .AnnouncedAddress }} attribute_0: UN-LOCODE:{{ .Attribute.Locode }} relay: {{ .Relay }} # start Storage node in relay mode without bootstrapping into the Network map - subnet: - exit_zero: false # toggle entrance to zero subnet (overrides corresponding attribute and occurrence in entries) - entries: [] # list of IDs of subnets to enter in a text format of NeoFS API protocol (overrides corresponding attributes) grpc: num: 1 # total number of listener endpoints @@ -38,9 +35,9 @@ control: morph: dial_timeout: 20s # timeout for side chain NEO RPC client connection cache_ttl: 15s # use TTL cache for side chain GET operations - rpc_endpoint: # side chain N3 RPC endpoints + endpoints: # side chain N3 RPC endpoints {{- range .MorphRPC }} - - address: wss://{{.}}/ws{{end}} + - wss://{{.}}/ws{{end}} {{if not .Relay }} storage: shard_pool_size: 15 # size of per-shard worker pools used for PUT operations diff --git a/cmd/neofs-cli/modules/container/create.go b/cmd/neofs-cli/modules/container/create.go index a3eea1d0b5..52bb1422c8 100644 --- a/cmd/neofs-cli/modules/container/create.go +++ b/cmd/neofs-cli/modules/container/create.go @@ -16,7 +16,6 @@ import ( cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/netmap" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/spf13/cobra" ) @@ -28,7 +27,6 @@ var ( containerAwait bool containerName string containerNoTimestamp bool - containerSubnet string force bool ) @@ -71,15 +69,6 @@ It will be stored in sidechain when inner ring will accepts it.`, } } - if containerSubnet != "" { - var subnetID subnetid.ID - - err = subnetID.DecodeString(containerSubnet) - common.ExitOnErr(cmd, "could not parse subnetID: %w", err) - - placementPolicy.RestrictSubnet(subnetID) - } - var cnr container.Container cnr.Init() @@ -175,7 +164,6 @@ func initContainerCreateCmd() { "Increases default execution timeout to %.0fs", awaitTimeout.Seconds())) // simple %s notation prints 1m0s https://github.com/golang/go/issues/39064 flags.StringVar(&containerName, "name", "", "Container name attribute") flags.BoolVar(&containerNoTimestamp, "disable-timestamp", false, "Disable timestamp container attribute") - flags.StringVar(&containerSubnet, "subnet", "", "String representation of container subnetwork") flags.BoolVarP(&force, commonflags.ForceFlag, commonflags.ForceFlagShorthand, false, "Skip placement validity check") } diff --git a/cmd/neofs-ir/defaults.go b/cmd/neofs-ir/defaults.go index 53d66888f1..331d31c80c 100644 --- a/cmd/neofs-ir/defaults.go +++ b/cmd/neofs-ir/defaults.go @@ -74,7 +74,6 @@ func defaultConfiguration(cfg *viper.Viper) { cfg.SetDefault("workers.container", "10") cfg.SetDefault("workers.alphabet", "10") cfg.SetDefault("workers.reputation", "10") - cfg.SetDefault("workers.subnet", "10") cfg.SetDefault("netmap_cleaner.enabled", true) cfg.SetDefault("netmap_cleaner.threshold", 3) diff --git a/cmd/neofs-node/config/morph/config.go b/cmd/neofs-node/config/morph/config.go index a39b8ba0c7..e50d679168 100644 --- a/cmd/neofs-node/config/morph/config.go +++ b/cmd/neofs-node/config/morph/config.go @@ -2,7 +2,6 @@ package morphconfig import ( "fmt" - "strconv" "time" "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config" @@ -30,24 +29,9 @@ const ( // // Throws panic if list is empty. func Endpoints(c *config.Config) []string { - var endpointsDeprecated []string - - sub := c.Sub(subsection).Sub("rpc_endpoint") - for i := 0; ; i++ { - s := sub.Sub(strconv.FormatInt(int64(i), 10)) - addr := config.StringSafe(s, "address") - if addr == "" { - break - } - - endpointsDeprecated = append(endpointsDeprecated, addr) - } - endpoints := config.StringSliceSafe(c.Sub(subsection), "endpoints") - endpoints = append(endpoints, endpointsDeprecated...) - if len(endpoints) == 0 { - panic(fmt.Errorf("no morph chain RPC endpoints, see `morph.rpc_endpoint` section")) + panic(fmt.Errorf("no morph chain RPC endpoints, see `morph.endpoints` section")) } return endpoints } diff --git a/cmd/neofs-node/config/node/config.go b/cmd/neofs-node/config/node/config.go index 315956434e..d006d4443c 100644 --- a/cmd/neofs-node/config/node/config.go +++ b/cmd/neofs-node/config/node/config.go @@ -177,33 +177,6 @@ func (p PersistentStateConfig) Path() string { return PersistentStatePathDefault } -// SubnetConfig represents node configuration related to subnets. -type SubnetConfig config.Config - -// Init initializes SubnetConfig from "subnet" sub-section of "node" section -// of the root config. -func (x *SubnetConfig) Init(root config.Config) { - *x = SubnetConfig(*root.Sub(subsection).Sub("subnet")) -} - -// ExitZero returns the value of "exit_zero" config parameter as bool. -// Returns false if the value can not be cast. -func (x SubnetConfig) ExitZero() bool { - return config.BoolSafe((*config.Config)(&x), "exit_zero") -} - -// IterateSubnets casts the value of "entries" config parameter to string slice, -// iterates over all of its elements and passes them to f. -// -// Does nothing if the value can not be cast to string slice. -func (x SubnetConfig) IterateSubnets(f func(string)) { - ids := config.StringSliceSafe((*config.Config)(&x), "entries") - - for i := range ids { - f(ids[i]) - } -} - // Notification returns structure that provides access to "notification" // subsection of "node" section. func Notification(c *config.Config) NotificationConfig { diff --git a/cmd/neofs-node/config/node/config_test.go b/cmd/neofs-node/config/node/config_test.go index f0bf39eca9..d12d703774 100644 --- a/cmd/neofs-node/config/node/config_test.go +++ b/cmd/neofs-node/config/node/config_test.go @@ -52,20 +52,6 @@ func TestNodeSection(t *testing.T) { require.Equal(t, "", notificationDefaultCertPath) require.Equal(t, "", notificationDefaultKeyPath) require.Equal(t, "", notificationDefaultCAPath) - - var subnetCfg SubnetConfig - - subnetCfg.Init(*empty) - - require.False(t, subnetCfg.ExitZero()) - - called := false - - subnetCfg.IterateSubnets(func(string) { - called = true - }) - - require.False(t, called) }) const path = "../../../../config/example/node" @@ -143,20 +129,6 @@ func TestNodeSection(t *testing.T) { require.Equal(t, "/cert/path", notificationCertPath) require.Equal(t, "/key/path", notificationKeyPath) require.Equal(t, "/ca/path", notificationCAPath) - - var subnetCfg SubnetConfig - - subnetCfg.Init(*c) - - require.True(t, subnetCfg.ExitZero()) - - var ids []string - - subnetCfg.IterateSubnets(func(id string) { - ids = append(ids, id) - }) - - require.Equal(t, []string{"123", "456", "789"}, ids) } configtest.ForEachFileType(path, fileConfigTest) diff --git a/cmd/neofs-node/netmap.go b/cmd/neofs-node/netmap.go index cdee2e3c73..59cb0c3736 100644 --- a/cmd/neofs-node/netmap.go +++ b/cmd/neofs-node/netmap.go @@ -6,7 +6,6 @@ import ( "fmt" netmapGRPC "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc" - nodeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/node" "github.com/nspcc-dev/neofs-node/pkg/core/netmap" "github.com/nspcc-dev/neofs-node/pkg/metrics" nmClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" @@ -17,7 +16,6 @@ import ( "github.com/nspcc-dev/neofs-node/pkg/services/control" netmapService "github.com/nspcc-dev/neofs-node/pkg/services/netmap" netmapSDK "github.com/nspcc-dev/neofs-sdk-go/netmap" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" "github.com/nspcc-dev/neofs-sdk-go/version" "go.uber.org/atomic" "go.uber.org/zap" @@ -141,8 +139,6 @@ func initNetmapService(c *cfg) { parseAttributes(c) c.cfgNodeInfo.localInfo.SetOffline() - readSubnetCfg(c) - if c.cfgMorph.client == nil { initMorphComponents(c) } @@ -219,29 +215,6 @@ func initNetmapService(c *cfg) { }) } -func readSubnetCfg(c *cfg) { - var subnetCfg nodeconfig.SubnetConfig - - subnetCfg.Init(*c.appCfg) - - var ( - id subnetid.ID - err error - ) - - subnetCfg.IterateSubnets(func(idTxt string) { - err = id.DecodeString(idTxt) - fatalOnErrDetails("parse subnet entry", err) - - c.cfgNodeInfo.localInfo.EnterSubnet(id) - }) - - if subnetCfg.ExitZero() { - subnetid.MakeZero(&id) - c.cfgNodeInfo.localInfo.ExitSubnet(id) - } -} - // bootstrapNode adds current node to the Network map. // Must be called after initNetmapService. func bootstrapNode(c *cfg) { diff --git a/config/example/ir.env b/config/example/ir.env index 6e092ee8bd..d40914a088 100644 --- a/config/example/ir.env +++ b/config/example/ir.env @@ -48,7 +48,6 @@ NEOFS_IR_WORKERS_CONTAINER=10 NEOFS_IR_WORKERS_NEOFS=10 NEOFS_IR_WORKERS_NETMAP=10 NEOFS_IR_WORKERS_REPUTATION=10 -NEOFS_IR_WORKERS_SUBNET=10 NEOFS_IR_AUDIT_TIMEOUT_GET=5s NEOFS_IR_AUDIT_TIMEOUT_HEAD=5s @@ -74,7 +73,6 @@ NEOFS_IR_CONTRACTS_NEOFSID=9f5866decbc751a099e74c7c7bc89f609201755a NEOFS_IR_CONTRACTS_NETMAP=83c600c81d47a1b1b7cf58eb49ae7ee7240dc742 NEOFS_IR_CONTRACTS_PROXY=abc8794bb40a21f2db5f21ae62741eb46c8cad1c NEOFS_IR_CONTRACTS_REPUTATION=d793b842ff0c103fe89e385069e82a27602135ff -NEOFS_IR_CONTRACTS_SUBNET=e9266864d3c562c6e17f2bb9cb1392aaa293d93a NEOFS_IR_CONTRACTS_ALPHABET_AMOUNT=7 NEOFS_IR_CONTRACTS_ALPHABET_AZ=c1d211fceeb4b1dc76b8e4054d11fdf887e418ea NEOFS_IR_CONTRACTS_ALPHABET_BUKY=e2ba789320899658b100f331bdebb74474757920 diff --git a/config/example/ir.yaml b/config/example/ir.yaml index 9e0f532850..2e6efdb795 100644 --- a/config/example/ir.yaml +++ b/config/example/ir.yaml @@ -150,7 +150,6 @@ workers: neofs: 10 # Number of workers to process events from neofs contracts in parallel netmap: 10 # Number of workers to process events from netmap contract in parallel reputation: 10 # Number of workers to process events from reputation contract in parallel - subnet: 10 # Number of workers to process events from subnet contract in parallel audit: timeout: @@ -184,7 +183,6 @@ contracts: netmap: 83c600c81d47a1b1b7cf58eb49ae7ee7240dc742 # Optional: override address of netmap contract in sidechain proxy: abc8794bb40a21f2db5f21ae62741eb46c8cad1c # Optional: override address of proxy contract in sidechain reputation: d793b842ff0c103fe89e385069e82a27602135ff # Optional: override address of reputation contract in sidechain - subnet: e9266864d3c562c6e17f2bb9cb1392aaa293d93a # Optional: override address of subnet contract in sidechain alphabet: amount: 7 # Optional: override amount of alphabet contracts az: c1d211fceeb4b1dc76b8e4054d11fdf887e418ea # Optional: override address of az alphabet contract in sidechain diff --git a/config/example/node.env b/config/example/node.env index b1158d06bd..2e81750530 100644 --- a/config/example/node.env +++ b/config/example/node.env @@ -19,8 +19,6 @@ NEOFS_NODE_ATTRIBUTE_1="UN-LOCODE:RU MSK" NEOFS_NODE_RELAY=true NEOFS_NODE_PERSISTENT_SESSIONS_PATH=/sessions NEOFS_NODE_PERSISTENT_STATE_PATH=/state -NEOFS_NODE_SUBNET_EXIT_ZERO=true -NEOFS_NODE_SUBNET_ENTRIES=123 456 789 NEOFS_NODE_NOTIFICATION_ENABLED=true NEOFS_NODE_NOTIFICATION_ENDPOINT=tls://localhost:4222 NEOFS_NODE_NOTIFICATION_TIMEOUT=6s diff --git a/config/example/node.json b/config/example/node.json index c667b42ddc..908662ba1a 100644 --- a/config/example/node.json +++ b/config/example/node.json @@ -34,14 +34,6 @@ "persistent_state": { "path": "/state" }, - "subnet": { - "exit_zero": true, - "entries": [ - "123", - "456", - "789" - ] - }, "notification": { "enabled": true, "endpoint": "tls://localhost:4222", diff --git a/config/example/node.yaml b/config/example/node.yaml index d01739cca2..cfd3a6a956 100644 --- a/config/example/node.yaml +++ b/config/example/node.yaml @@ -29,12 +29,6 @@ node: path: /sessions # path to persistent session tokens file of Storage node (default: in-memory sessions) persistent_state: path: /state # path to persistent state file of Storage node - subnet: - exit_zero: true # toggle entrance to zero subnet (overrides corresponding attribute and occurrence in `entries`) - entries: # list of IDs of subnets to enter in a text format of NeoFS API protocol (overrides corresponding attributes) - - 123 - - 456 - - 789 notification: enabled: true # turn on object notification service endpoint: "tls://localhost:4222" # notification server endpoint diff --git a/docs/cli-adm.md b/docs/cli-adm.md index a80d114e23..e94e9c1dbe 100644 --- a/docs/cli-adm.md +++ b/docs/cli-adm.md @@ -70,11 +70,15 @@ credentials: # passwords for consensus node / alphabet wallets #### Network maintenance +- `dump-names` allows to walk through NNS names and see their expirations. + - `set-config` add/update configuration values in the Netmap contract. - `force-new-epoch` increments NeoFS epoch number and executes new epoch handlers in NeoFS nodes. +- `renew-domain` updates expiration date of the given domain for one year. + - `refill-gas` transfers sidechain GAS to the specified wallet. - `update-contracts` updates contracts to a new version. diff --git a/docs/storage-node-configuration.md b/docs/storage-node-configuration.md index 8e31fe9f0c..3ba935a3f3 100644 --- a/docs/storage-node-configuration.md +++ b/docs/storage-node-configuration.md @@ -124,7 +124,6 @@ contracts: | `container` | `hash160` | | Container contract hash. | | `netmap` | `hash160` | | Netmap contract hash. | | `reputation` | `hash160` | | Reputation contract hash. | -| `subnet` | `hash160` | | Subnet contract hash. | # `morph` section @@ -132,9 +131,9 @@ contracts: morph: dial_timeout: 30s cache_ttl: 15s - rpc_endpoint: - - address: wss://rpc1.morph.fs.neo.org:40341/ws - - address: wss://rpc2.morph.fs.neo.org:40341/ws + endpoints: + - wss://rpc1.morph.fs.neo.org:40341/ws + - wss://rpc2.morph.fs.neo.org:40341/ws ``` | Parameter | Type | Default value | Description | @@ -290,10 +289,6 @@ node: path: /sessions persistent_state: path: /state - subnet: - exit_zero: false - entries: - - 123 notification: enabled: true endpoint: tls://localhost:4222 @@ -313,7 +308,6 @@ node: | `relay` | `bool` | | Enable relay mode. | | `persistent_sessions` | [Persistent sessions config](#persistent_sessions-subsection) | | Persistent session token store configuration. | | `persistent_state` | [Persistent state config](#persistent_state-subsection) | | Persistent state configuration. | -| `subnet` | [Subnet config](#subnet-subsection) | | Subnet configuration. | | `notification` | [Notification config](#notification-subsection) | | NATS configuration. | @@ -342,14 +336,6 @@ It is used to correctly handle node restarts or crashes. |-----------|----------|------------------------|------------------------| | `path` | `string` | `.neofs-storage-state` | Path to the database. | -## `subnet` subsection -This is an advanced section, use with caution. - -| Parameter | Type | Default value | Description | -|-------------|------------|---------------|------------------------------------------------------| -| `exit_zero` | `bool` | `false` | Exit from the default subnet. | -| `entries` | `[]uint32` | | List of non-default subnet ID this node belongs to. | - ## `notification` subsection This is an advanced section, use with caution. diff --git a/pkg/innerring/config.go b/pkg/innerring/config.go index 0315216ab3..464ba962d5 100644 --- a/pkg/innerring/config.go +++ b/pkg/innerring/config.go @@ -22,12 +22,7 @@ import ( // checks if Inner Ring app is configured to be launched in local consensus // mode. func isLocalConsensusMode(cfg *viper.Viper) bool { - const morphRPCSectionDeprecated = "morph.endpoint.client" - // first expression required for ENVs in which nesting breaks - deprecatedNotSet := !cfg.IsSet(morphRPCSectionDeprecated+".0.address") && !cfg.IsSet(morphRPCSectionDeprecated) - actualNotSet := !cfg.IsSet("morph.endpoints") - - return deprecatedNotSet && actualNotSet + return !cfg.IsSet("morph.endpoints") } func parseBlockchainConfig(v *viper.Viper, _logger *logger.Logger) (c blockchain.Config, err error) { diff --git a/pkg/innerring/config_test.go b/pkg/innerring/config_test.go index 1af5a65413..2945c34a3f 100644 --- a/pkg/innerring/config_test.go +++ b/pkg/innerring/config_test.go @@ -401,7 +401,7 @@ func TestIsLocalConsensusMode(t *testing.T) { v.SetEnvPrefix("neofs_ir") v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - const envKey = "NEOFS_IR_MORPH_ENDPOINT_CLIENT_0_ADDRESS" + const envKey = "NEOFS_IR_MORPH_ENDPOINTS" err := os.Unsetenv(envKey) require.NoError(t, err) @@ -419,15 +419,14 @@ func TestIsLocalConsensusMode(t *testing.T) { v.SetConfigType("yaml") err := v.ReadConfig(strings.NewReader(` morph: - endpoint: - client: - - address: ws://morph-chain:30333/ws + endpoints: + - ws://morph-chain:30333/ws `)) require.NoError(t, err) require.False(t, isLocalConsensusMode(v)) - resetConfig(t, v, "morph.endpoint.client") + resetConfig(t, v, "morph.endpoints") require.True(t, isLocalConsensusMode(v)) }) diff --git a/pkg/innerring/contracts.go b/pkg/innerring/contracts.go index 3c7c153248..747bbcf5ff 100644 --- a/pkg/innerring/contracts.go +++ b/pkg/innerring/contracts.go @@ -24,7 +24,6 @@ type contracts struct { proxy util.Uint160 // in morph processing util.Uint160 // in mainnet reputation util.Uint160 // in morph - subnet util.Uint160 // in morph neofsID util.Uint160 // in morph alphabet alphabetContracts // in morph @@ -69,7 +68,6 @@ func initContracts(ctx context.Context, _logger *logger.Logger, cfg *viper.Viper {"contracts.container", client.NNSContainerContractName, &result.container}, {"contracts.audit", client.NNSAuditContractName, &result.audit}, {"contracts.reputation", client.NNSReputationContractName, &result.reputation}, - {"contracts.subnet", client.NNSSubnetworkContractName, &result.subnet}, {"contracts.neofsid", client.NNSNeoFSIDContractName, &result.neofsID}, } diff --git a/pkg/innerring/indexer.go b/pkg/innerring/indexer.go index ac5fb93efb..78c50ca29e 100644 --- a/pkg/innerring/indexer.go +++ b/pkg/innerring/indexer.go @@ -82,6 +82,16 @@ func (s *innerRingIndexer) update() (ind indexes, err error) { return s.ind, nil } +func (s *innerRingIndexer) reset() { + s.Lock() + defer s.Unlock() + + // zero time, every real time is expected to + // be _much later_ after that time; `update` + // will be forced to make RPC calls + s.lastAccess = time.Time{} +} + func (s *innerRingIndexer) InnerRingIndex() (int32, error) { ind, err := s.update() if err != nil { diff --git a/pkg/innerring/innerring.go b/pkg/innerring/innerring.go index 5452f381c4..5ff0ee3c20 100644 --- a/pkg/innerring/innerring.go +++ b/pkg/innerring/innerring.go @@ -25,7 +25,6 @@ import ( nodevalidator "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation" addrvalidator "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/maddress" statevalidation "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/state" - subnetvalidator "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/subnet" "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/reputation" "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement" auditSettlement "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/audit" @@ -39,7 +38,6 @@ import ( "github.com/nspcc-dev/neofs-node/pkg/morph/client/neofsid" nmClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" repClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/reputation" - morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet" "github.com/nspcc-dev/neofs-node/pkg/morph/event" "github.com/nspcc-dev/neofs-node/pkg/morph/subscriber" "github.com/nspcc-dev/neofs-node/pkg/morph/timer" @@ -73,17 +71,20 @@ type ( blockTimers []*timer.BlockTimer epochTimer *timer.BlockTimer - // global state morphClient *client.Client mainnetClient *client.Client + auditClient *auditClient.Client + balanceClient *balanceClient.Client + netmapClient *nmClient.Client + + auditTaskManager *audittask.Manager + + // global state epochCounter atomic.Uint64 epochDuration atomic.Uint64 statusIndex *innerRingIndexer - precision precision.Fixed8Converter - auditClient *auditClient.Client + precision uint32 // not changeable healthStatus atomic.Value - balanceClient *balanceClient.Client - netmapClient *nmClient.Client persistate *state.PersistentStorage // metrics @@ -98,7 +99,7 @@ type ( pubKey []byte contracts *contracts predefinedValidators keys.PublicKeys - initialEpochTickDelta uint32 + initialEpochTickDelta atomic.Uint32 withoutMainNet bool // runtime processors @@ -127,8 +128,6 @@ type ( // should report start errors // to the application. runners []func(chan<- error) error - - subnetHandler } chainParams struct { @@ -211,7 +210,9 @@ func (s *Server) Start(ctx context.Context, intError chan<- error) (err error) { // tick initial epoch initialEpochTicker := timer.NewOneTickTimer( - timer.StaticBlockMeter(s.initialEpochTickDelta), + func() (uint32, error) { + return s.initialEpochTickDelta.Load(), nil + }, func() { s.netmapProcessor.HandleNewEpochTick(timerEvent.NewEpochTick{}) }) @@ -343,7 +344,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan log.Warn("can't get last processed side chain block number", zap.String("error", err.Error())) } - morphChain := &chainParams{ + morphChain := chainParams{ log: log, cfg: cfg, name: morphPrefix, @@ -438,7 +439,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan server.key = acc.PrivateKey() morphChain.key = server.key - server.morphClient, err = createClient(ctx, morphChain, errChan) + server.morphClient, err = server.createClient(ctx, morphChain, errChan) if err != nil { return nil, err } @@ -474,7 +475,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan mainnetChain.from = fromMainChainBlock // create mainnet client - server.mainnetClient, err = createClient(ctx, mainnetChain, errChan) + server.mainnetClient, err = server.createClient(ctx, mainnetChain, errChan) if err != nil { return nil, err } @@ -539,13 +540,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan return nil, err } - // form morph container client's options - morphCnrOpts := make([]cntClient.Option, 0, 3) - morphCnrOpts = append(morphCnrOpts, - cntClient.AsAlphabet(), - ) - - cnrClient, err := cntClient.NewFromMorph(server.morphClient, server.contracts.container, 0, morphCnrOpts...) + cnrClient, err := cntClient.NewFromMorph(server.morphClient, server.contracts.container, 0, cntClient.AsAlphabet()) if err != nil { return nil, err } @@ -560,6 +555,11 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan return nil, err } + server.precision, err = server.balanceClient.Decimals() + if err != nil { + return nil, fmt.Errorf("can't read balance contract precision: %w", err) + } + repClient, err := repClient.NewFromMorph(server.morphClient, server.contracts.reputation, 0, repClient.AsAlphabet()) if err != nil { return nil, err @@ -576,20 +576,6 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan return nil, err } - // initialize morph client of Subnet contract - clientMode := morphsubnet.NotaryAlphabet - - subnetInitPrm := morphsubnet.InitPrm{} - subnetInitPrm.SetBaseClient(server.morphClient) - subnetInitPrm.SetContractAddress(server.contracts.subnet) - subnetInitPrm.SetMode(clientMode) - - subnetClient := &morphsubnet.Client{} - err = subnetClient.Init(subnetInitPrm) - if err != nil { - return nil, fmt.Errorf("could not initialize subnet client: %w", err) - } - var irf irFetcher if server.withoutMainNet { @@ -622,7 +608,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan porPoolSize := cfg.GetInt("audit.por.pool_size") // create audit processor dependencies - auditTaskManager := audittask.New( + server.auditTaskManager = audittask.New( audittask.WithQueueCapacity(cfg.GetUint32("audit.task.queue_capacity")), audittask.WithWorkerPool(auditPool), audittask.WithLogger(log), @@ -636,7 +622,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan }), ) - server.workers = append(server.workers, auditTaskManager.Listen) + server.workers = append(server.workers, server.auditTaskManager.Listen) // create audit processor auditProcessor, err := audit.New(&audit.Params{ @@ -648,7 +634,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan SGSource: clientCache, Key: &server.key.PrivateKey, RPCSearchTimeout: cfg.GetDuration("audit.timeout.search"), - TaskManager: auditTaskManager, + TaskManager: server.auditTaskManager, Reporter: server, }) if err != nil { @@ -704,15 +690,6 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan return nil, err } - subnetValidator, err := subnetvalidator.New( - subnetvalidator.Prm{ - SubnetClient: subnetClient, - }, - ) - if err != nil { - return nil, err - } - var alphaSync event.Handler if server.withoutMainNet || cfg.GetBool("governance.disable") { @@ -773,10 +750,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan &netMapCandidateStateValidator, addrvalidator.New(), locodeValidator, - subnetValidator, ), - SubnetContract: &server.contracts.subnet, - NodeStateSettings: netSettings, }) if err != nil { @@ -796,7 +770,6 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan ContainerClient: cnrClient, NeoFSIDClient: neofsIDClient, NetworkState: server.netmapClient, - SubnetClient: subnetClient, }) if err != nil { return nil, err @@ -807,6 +780,8 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan return nil, err } + precisionConverter := precision.NewConverter(server.precision) + // create balance processor balanceProcessor, err := balance.New(&balance.Params{ Log: log, @@ -814,7 +789,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan NeoFSClient: neofsCli, BalanceSC: server.contracts.balance, AlphabetState: server, - Converter: &server.precision, + Converter: precisionConverter, }) if err != nil { return nil, err @@ -837,7 +812,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan MorphClient: server.morphClient, EpochState: server, AlphabetState: server, - Converter: &server.precision, + Converter: precisionConverter, MintEmitCacheSize: cfg.GetInt("emit.mint.cache_size"), MintEmitThreshold: cfg.GetUint64("emit.mint.threshold"), MintEmitValue: fixedn.Fixed8(cfg.GetInt64("emit.mint.value")), @@ -970,10 +945,6 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan log.Info("no Control server endpoint specified, service is disabled") } - server.initSubnet(subnetConfig{ - queueSize: cfg.GetUint32("workers.subnet"), - }) - if cfg.GetString("prometheus.address") != "" { m := metrics.NewInnerRingMetrics(misc.Version) server.metrics = &m @@ -982,7 +953,7 @@ func New(ctx context.Context, log *logger.Logger, cfg *viper.Viper, errChan chan return server, nil } -func createListener(ctx context.Context, cli *client.Client, p *chainParams) (event.Listener, error) { +func createListener(ctx context.Context, cli *client.Client, p chainParams) (event.Listener, error) { // listenerPoolCap is a capacity of a // worker pool inside the listener. It // is used to prevent blocking in neo-go: @@ -1017,20 +988,8 @@ func createListener(ctx context.Context, cli *client.Client, p *chainParams) (ev return listener, err } -func createClient(ctx context.Context, p *chainParams, errChan chan<- error) (*client.Client, error) { +func (s *Server) createClient(ctx context.Context, p chainParams, errChan chan<- error) (*client.Client, error) { endpoints := p.cfg.GetStringSlice(p.name + ".endpoints") - - // deprecated endpoints with priorities - section := p.name + ".endpoint.client" - for i := 0; ; i++ { - addr := p.cfg.GetString(fmt.Sprintf("%s.%d.%s", section, i, "address")) - if addr == "" { - break - } - - endpoints = append(endpoints, addr) - } - if len(endpoints) == 0 { return nil, fmt.Errorf("%s chain client endpoints not provided", p.name) } @@ -1044,6 +1003,18 @@ func createClient(ctx context.Context, p *chainParams, errChan chan<- error) (*c client.WithEndpoints(endpoints), client.WithReconnectionRetries(p.cfg.GetInt(p.name+".reconnections_number")), client.WithReconnectionsDelay(p.cfg.GetDuration(p.name+".reconnections_delay")), + client.WithConnSwitchCallback(func() { + var err error + + if p.name == morphPrefix { + err = s.restartMorph() + } else { + err = s.restartMainChain() + } + if err != nil { + errChan <- fmt.Errorf("internal services' restart after RPC reconnection to the %s: %w", p.name, err) + } + }), client.WithConnLostCallback(func() { errChan <- fmt.Errorf("%s chain connection has been lost", p.name) }), @@ -1088,28 +1059,22 @@ func (s *Server) initConfigFromBlockchain() error { return fmt.Errorf("can't read epoch duration: %w", err) } - // get balance precision - balancePrecision, err := s.balanceClient.Decimals() + // get next epoch delta tick + delta, err := s.nextEpochBlockDelta() if err != nil { - return fmt.Errorf("can't read balance contract precision: %w", err) + return err } s.epochCounter.Store(epoch) s.epochDuration.Store(epochDuration) - s.precision.SetBalancePrecision(balancePrecision) - - // get next epoch delta tick - s.initialEpochTickDelta, err = s.nextEpochBlockDelta() - if err != nil { - return err - } + s.initialEpochTickDelta.Store(delta) s.log.Debug("read config from blockchain", zap.Bool("active", s.IsActive()), zap.Bool("alphabet", s.IsAlphabet()), zap.Uint64("epoch", epoch), - zap.Uint32("precision", balancePrecision), - zap.Uint32("init_epoch_tick_delta", s.initialEpochTickDelta), + zap.Uint32("precision", s.precision), + zap.Uint32("init_epoch_tick_delta", delta), ) return nil @@ -1163,3 +1128,30 @@ func (s *Server) newEpochTickHandlers() []newEpochHandler { return newEpochHandlers } + +func (s *Server) restartMorph() error { + s.log.Info("restarting internal services because of RPC connection loss...") + + s.auditTaskManager.Reset() + s.statusIndex.reset() + + err := s.initConfigFromBlockchain() + if err != nil { + return fmt.Errorf("side chain config reinitialization: %w", err) + } + + for _, t := range s.blockTimers { + err = t.Reset() + if err != nil { + return fmt.Errorf("could not reset block timers: %w", err) + } + } + + s.log.Info("internal services have been restarted after RPC connection loss...") + + return nil +} + +func (s *Server) restartMainChain() error { + return nil +} diff --git a/pkg/innerring/processors/container/process_container.go b/pkg/innerring/processors/container/process_container.go index ea905320e2..98767f1eea 100644 --- a/pkg/innerring/processors/container/process_container.go +++ b/pkg/innerring/processors/container/process_container.go @@ -5,13 +5,11 @@ import ( "github.com/nspcc-dev/neo-go/pkg/network/payload" cntClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" - morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet" "github.com/nspcc-dev/neofs-node/pkg/morph/event" containerEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/container" containerSDK "github.com/nspcc-dev/neofs-sdk-go/container" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/session" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" "go.uber.org/zap" ) @@ -76,12 +74,6 @@ func (cp *Processor) checkPutContainer(ctx *putContainerContext) error { return fmt.Errorf("auth container creation: %w", err) } - // check owner allowance in the subnetwork - err = checkSubnet(cp.subnetClient, cnr) - if err != nil { - return fmt.Errorf("incorrect subnetwork: %w", err) - } - // check homomorphic hashing setting err = checkHomomorphicHashing(cp.netState, cnr) if err != nil { @@ -213,29 +205,6 @@ func checkNNS(ctx *putContainerContext, cnr containerSDK.Container) error { return nil } -func checkSubnet(subCli *morphsubnet.Client, cnr containerSDK.Container) error { - prm := morphsubnet.UserAllowedPrm{} - - subID := cnr.PlacementPolicy().Subnet() - if subnetid.IsZero(subID) { - return nil - } - - prm.SetID(subID.Marshal()) - prm.SetClient(cnr.Owner().WalletBytes()) - - res, err := subCli.UserAllowed(prm) - if err != nil { - return fmt.Errorf("could not check user in contract: %w", err) - } - - if !res.Allowed() { - return fmt.Errorf("user is not allowed to create containers in %v subnetwork", subID) - } - - return nil -} - func checkHomomorphicHashing(ns NetworkState, cnr containerSDK.Container) error { netSetting, err := ns.HomomorphicHashDisabled() if err != nil { diff --git a/pkg/innerring/processors/container/processor.go b/pkg/innerring/processors/container/processor.go index b10f6b309d..07d7ec8e8c 100644 --- a/pkg/innerring/processors/container/processor.go +++ b/pkg/innerring/processors/container/processor.go @@ -7,7 +7,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" "github.com/nspcc-dev/neofs-node/pkg/morph/client/neofsid" - morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet" "github.com/nspcc-dev/neofs-node/pkg/morph/event" containerEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/container" "github.com/nspcc-dev/neofs-node/pkg/util/logger" @@ -28,7 +27,6 @@ type ( alphabetState AlphabetState cnrClient *container.Client // notary must be enabled idClient *neofsid.Client - subnetClient *morphsubnet.Client netState NetworkState } @@ -39,7 +37,6 @@ type ( AlphabetState AlphabetState ContainerClient *container.Client NeoFSIDClient *neofsid.Client - SubnetClient *morphsubnet.Client NetworkState NetworkState } ) @@ -75,8 +72,6 @@ func New(p *Params) (*Processor, error) { return nil, errors.New("ir/container: NeoFS ID client is not set") case p.NetworkState == nil: return nil, errors.New("ir/container: network state is not set") - case p.SubnetClient == nil: - return nil, errors.New("ir/container: subnet client is not set") } p.Log.Debug("container worker pool", zap.Int("size", p.PoolSize)) @@ -93,7 +88,6 @@ func New(p *Params) (*Processor, error) { cnrClient: p.ContainerClient, idClient: p.NeoFSIDClient, netState: p.NetworkState, - subnetClient: p.SubnetClient, }, nil } diff --git a/pkg/innerring/processors/netmap/handlers.go b/pkg/innerring/processors/netmap/handlers.go index fea72672ef..a1d6b239b1 100644 --- a/pkg/innerring/processors/netmap/handlers.go +++ b/pkg/innerring/processors/netmap/handlers.go @@ -6,7 +6,6 @@ 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" - subnetevents "github.com/nspcc-dev/neofs-node/pkg/morph/event/subnet" "go.uber.org/zap" ) @@ -100,22 +99,3 @@ func (np *Processor) handleCleanupTick(ev event.Event) { zap.Int("capacity", np.pool.Cap())) } } - -func (np *Processor) handleRemoveNode(ev event.Event) { - removeNode := ev.(subnetevents.RemoveNode) - - np.log.Info("notification", - zap.String("type", "remove node from subnet"), - zap.String("subnetID", hex.EncodeToString(removeNode.SubnetworkID())), - zap.String("key", hex.EncodeToString(removeNode.Node())), - ) - - err := np.pool.Submit(func() { - np.processRemoveSubnetNode(removeNode) - }) - if err != nil { - // there system can be moved into controlled degradation stage - np.log.Warn("netmap worker pool drained", - zap.Int("capacity", np.pool.Cap())) - } -} diff --git a/pkg/innerring/processors/netmap/nodevalidation/subnet/calls.go b/pkg/innerring/processors/netmap/nodevalidation/subnet/calls.go deleted file mode 100644 index 399cf865fb..0000000000 --- a/pkg/innerring/processors/netmap/nodevalidation/subnet/calls.go +++ /dev/null @@ -1,42 +0,0 @@ -package subnet - -import ( - "fmt" - - morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet" - "github.com/nspcc-dev/neofs-sdk-go/netmap" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" -) - -// VerifyAndUpdate calls subnet contract's `NodeAllowed` method. -// Removes subnets that have not been approved by the contract. -func (v *Validator) VerifyAndUpdate(n *netmap.NodeInfo) error { - prm := morphsubnet.NodeAllowedPrm{} - - err := n.IterateSubnets(func(id subnetid.ID) error { - // every node can be bootstrapped - // to the zero subnetwork - if subnetid.IsZero(id) { - return nil - } - - prm.SetID(id.Marshal()) - prm.SetNode(n.PublicKey()) - - res, err := v.subnetClient.NodeAllowed(prm) - if err != nil { - return fmt.Errorf("could not call `NodeAllowed` contract method: %w", err) - } - - if !res.Allowed() { - return netmap.ErrRemoveSubnet - } - - return nil - }) - if err != nil { - return fmt.Errorf("could not verify subnet entrance of the node: %w", err) - } - - return nil -} diff --git a/pkg/innerring/processors/netmap/nodevalidation/subnet/validator.go b/pkg/innerring/processors/netmap/nodevalidation/subnet/validator.go deleted file mode 100644 index 7d6fb75608..0000000000 --- a/pkg/innerring/processors/netmap/nodevalidation/subnet/validator.go +++ /dev/null @@ -1,41 +0,0 @@ -package subnet - -import ( - "errors" - - morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet" -) - -// Validator is an utility that verifies node subnet -// allowance. -// -// For correct operation, Validator must be created -// using the constructor (New). After successful creation, -// the Validator is immediately ready to work through API. -type Validator struct { - subnetClient *morphsubnet.Client -} - -// Prm groups the required parameters of the Validator's constructor. -// -// All values must comply with the requirements imposed on them. -// Passing incorrect parameter values will result in constructor -// failure (error or panic depending on the implementation). -type Prm struct { - SubnetClient *morphsubnet.Client -} - -// New creates a new instance of the Validator. -// -// The created Validator does not require additional -// initialization and is completely ready for work. -func New(prm Prm) (*Validator, error) { - switch { - case prm.SubnetClient == nil: - return nil, errors.New("ir/nodeValidator: subnet client is not set") - } - - return &Validator{ - subnetClient: prm.SubnetClient, - }, nil -} diff --git a/pkg/innerring/processors/netmap/process_peers.go b/pkg/innerring/processors/netmap/process_peers.go index 2a61aecdc2..f8013058f2 100644 --- a/pkg/innerring/processors/netmap/process_peers.go +++ b/pkg/innerring/processors/netmap/process_peers.go @@ -1,14 +1,11 @@ package netmap import ( - "bytes" "encoding/hex" netmapclient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" netmapEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/netmap" - subnetEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/subnet" "github.com/nspcc-dev/neofs-sdk-go/netmap" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" "go.uber.org/zap" ) @@ -119,74 +116,3 @@ func (np *Processor) processUpdatePeer(ev netmapEvent.UpdatePeer) { np.log.Error("can't invoke netmap.UpdatePeer", zap.Error(err)) } } - -func (np *Processor) processRemoveSubnetNode(ev subnetEvent.RemoveNode) { - if !np.alphabetState.IsAlphabet() { - np.log.Info("non alphabet mode, ignore remove node from subnet notification") - return - } - - candidates, err := np.netmapClient.GetCandidates() - if err != nil { - np.log.Warn("could not get network map candidates", - zap.Error(err), - ) - return - } - - rawSubnet := ev.SubnetworkID() - var subnetToRemoveFrom subnetid.ID - - err = subnetToRemoveFrom.Unmarshal(rawSubnet) - if err != nil { - np.log.Warn("could not unmarshal subnet id", - zap.Error(err), - ) - return - } - - if subnetid.IsZero(subnetToRemoveFrom) { - np.log.Warn("got zero subnet in remove node notification") - return - } - - for i := range candidates { - if !bytes.Equal(candidates[i].PublicKey(), ev.Node()) { - continue - } - - err = candidates[i].IterateSubnets(func(subNetID subnetid.ID) error { - if subNetID.Equals(subnetToRemoveFrom) { - return netmap.ErrRemoveSubnet - } - - return nil - }) - if err != nil { - np.log.Warn("could not iterate over subnetworks of the node", zap.Error(err)) - np.log.Info("vote to remove node from netmap", zap.String("key", hex.EncodeToString(ev.Node()))) - - prm := netmapclient.UpdatePeerPrm{} - prm.SetKey(ev.Node()) - prm.SetHash(ev.TxHash()) - - err = np.netmapClient.UpdatePeerState(prm) - if err != nil { - np.log.Error("could not invoke netmap.UpdateState", zap.Error(err)) - return - } - } else { - prm := netmapclient.AddPeerPrm{} - prm.SetNodeInfo(candidates[i]) - prm.SetHash(ev.TxHash()) - - err = np.netmapClient.AddPeer(prm) - if err != nil { - np.log.Error("could not invoke netmap.AddPeer", zap.Error(err)) - return - } - } - - break - } -} diff --git a/pkg/innerring/processors/netmap/processor.go b/pkg/innerring/processors/netmap/processor.go index 058da34a26..2d8267bd94 100644 --- a/pkg/innerring/processors/netmap/processor.go +++ b/pkg/innerring/processors/netmap/processor.go @@ -5,13 +5,11 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" - "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/state" "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" nmClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" "github.com/nspcc-dev/neofs-node/pkg/morph/event" netmapEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/netmap" - subnetEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/subnet" "github.com/nspcc-dev/neofs-node/pkg/util/logger" "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/panjf2000/ants/v2" @@ -64,8 +62,6 @@ type ( netmapClient *nmClient.Client containerWrp *container.Client - subnetContract util.Uint160 - netmapSnapshot cleanupTable handleNewAudit event.Handler @@ -89,7 +85,6 @@ type ( CleanupEnabled bool CleanupThreshold uint64 // in epochs ContainerWrapper *container.Client - SubnetContract *util.Uint160 HandleAudit event.Handler AuditSettlementsHandler event.Handler @@ -130,8 +125,6 @@ func New(p *Params) (*Processor, error) { return nil, errors.New("ir/netmap: container contract wrapper is not set") case p.NodeValidator == nil: return nil, errors.New("ir/netmap: node validator is not set") - case p.SubnetContract == nil: - return nil, errors.New("ir/netmap: subnet contract script hash is not set") case p.NodeStateSettings == nil: return nil, errors.New("ir/netmap: node state settings is not set") } @@ -153,7 +146,6 @@ func New(p *Params) (*Processor, error) { containerWrp: p.ContainerWrapper, netmapSnapshot: newCleanupTable(p.CleanupEnabled, p.CleanupThreshold), handleNewAudit: p.HandleAudit, - subnetContract: *p.SubnetContract, handleAuditSettlements: p.AuditSettlementsHandler, @@ -169,17 +161,9 @@ func New(p *Params) (*Processor, error) { // ListenerNotificationParsers for the 'event.Listener' event producer. func (np *Processor) ListenerNotificationParsers() []event.NotificationParserInfo { - parsers := make([]event.NotificationParserInfo, 0, 3) + parsers := make([]event.NotificationParserInfo, 0, 1) var p event.NotificationParserInfo - - // remove node from subnetwork event - p.SetScriptHash(np.subnetContract) - p.SetType(removeNodeNotification) - p.SetParser(subnetEvent.ParseRemoveNode) - - parsers = append(parsers, p) - p.SetScriptHash(np.netmapClient.ContractAddress()) // new epoch event @@ -192,17 +176,9 @@ func (np *Processor) ListenerNotificationParsers() []event.NotificationParserInf // ListenerNotificationHandlers for the 'event.Listener' event producer. func (np *Processor) ListenerNotificationHandlers() []event.NotificationHandlerInfo { - handlers := make([]event.NotificationHandlerInfo, 0, 3) + handlers := make([]event.NotificationHandlerInfo, 0, 1) var i event.NotificationHandlerInfo - - // remove node from subnetwork event - i.SetScriptHash(np.subnetContract) - i.SetType(removeNodeNotification) - i.SetHandler(np.handleRemoveNode) - - handlers = append(handlers, i) - i.SetScriptHash(np.netmapClient.ContractAddress()) // new epoch handler diff --git a/pkg/innerring/processors/subnet/common.go b/pkg/innerring/processors/subnet/common.go deleted file mode 100644 index 08f3df0f1e..0000000000 --- a/pkg/innerring/processors/subnet/common.go +++ /dev/null @@ -1,22 +0,0 @@ -package subnetevents - -import ( - "fmt" - - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" -) - -// common interface of subnet notifications with subnet ID. -type eventWithID interface { - // ReadID reads identifier of the subnet. - ReadID(*subnetid.ID) error -} - -// an error which is returned on zero subnet operation attempt. -type zeroSubnetOp struct { - op string -} - -func (x zeroSubnetOp) Error() string { - return fmt.Sprintf("zero subnet %s", x.op) -} diff --git a/pkg/innerring/processors/subnet/common_test.go b/pkg/innerring/processors/subnet/common_test.go deleted file mode 100644 index 3834114f85..0000000000 --- a/pkg/innerring/processors/subnet/common_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package subnetevents - -import subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" - -type idEvent struct { - id subnetid.ID - - idErr error -} - -func (x idEvent) ReadID(id *subnetid.ID) error { - if x.idErr != nil { - return x.idErr - } - - *id = x.id - - return nil -} diff --git a/pkg/innerring/processors/subnet/put.go b/pkg/innerring/processors/subnet/put.go deleted file mode 100644 index fb0565b6e8..0000000000 --- a/pkg/innerring/processors/subnet/put.go +++ /dev/null @@ -1,82 +0,0 @@ -package subnetevents - -import ( - "errors" - "fmt" - - "github.com/nspcc-dev/neofs-sdk-go/subnet" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" - "github.com/nspcc-dev/neofs-sdk-go/user" -) - -// Put represents a notification about NeoFS subnet creation. -// Generated by a contract when intending to create a subnet. -type Put interface { - // Contains the ID of the subnet to be created. - eventWithID - - // ReadCreator reads the user ID of the subnet creator. - // Returns an error if the ID is missing. - ReadCreator(id *user.ID) error - - // ReadInfo reads information about a subnet to be created. - ReadInfo(info *subnet.Info) error -} - -// PutValidator asserts intent to create a subnet. -type PutValidator struct{} - -// errDiffOwner is returned when the subnet owners differ. -var errDiffOwner = errors.New("diff subnet owners") - -// errDiffID is returned when the subnet IDs differ. -var errDiffID = errors.New("diff subnet IDs") - -// Assert processes the attempt to create a subnet. It approves the creation through nil return. -// -// All read errors of Put are forwarded. -// -// It returns an error on: -// - zero subnet creation; -// - empty ID or different from the one wired into info; -// - empty owner ID or different from the one wired into info. -func (x PutValidator) Assert(event Put) error { - var err error - - // read ID - var id subnetid.ID - if err = event.ReadID(&id); err != nil { - return fmt.Errorf("read ID: %w", err) - } - - // prevent zero subnet creation - if subnetid.IsZero(id) { - return zeroSubnetOp{ - op: "creation", - } - } - - // read creator's user ID in NeoFS system - var creator user.ID - if err = event.ReadCreator(&creator); err != nil { - return fmt.Errorf("read creator: %w", err) - } - - // read information about the subnet - var info subnet.Info - if err = event.ReadInfo(&info); err != nil { - return fmt.Errorf("read info: %w", err) - } - - // check if the explicit ID equals to the one from info - if !subnet.AssertReference(info, id) { - return errDiffID - } - - // check if the explicit creator equals to the one from info - if !subnet.AssertOwnership(info, creator) { - return errDiffOwner - } - - return nil -} diff --git a/pkg/innerring/processors/subnet/put_test.go b/pkg/innerring/processors/subnet/put_test.go deleted file mode 100644 index 41c22b8e70..0000000000 --- a/pkg/innerring/processors/subnet/put_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package subnetevents - -import ( - "errors" - "testing" - - "github.com/nspcc-dev/neofs-sdk-go/user" - usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" - "github.com/stretchr/testify/require" - - "github.com/nspcc-dev/neofs-sdk-go/subnet" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" -) - -type put struct { - idEvent - - creator user.ID - - creatorErr error - - info subnet.Info - - infoErr error -} - -func (x put) ReadCreator(id *user.ID) error { - if x.creatorErr != nil { - return x.creatorErr - } - - *id = x.creator - - return nil -} - -func (x put) ReadInfo(info *subnet.Info) error { - if x.infoErr != nil { - return x.infoErr - } - - *info = x.info - - return nil -} - -func TestPutValidator_Assert(t *testing.T) { - var ( - v PutValidator - - e put - - err error - ) - - // read ID error - e.idErr = errors.New("id err") - - err = v.Assert(e) - require.ErrorIs(t, err, e.idErr) - - e.idErr = nil - - // zero subnet ID - subnetid.MakeZero(&e.id) - - err = v.Assert(e) - require.ErrorAs(t, err, new(zeroSubnetOp)) - - const idNum = 13 - e.id.SetNumeric(idNum) - - // read creator error - e.creatorErr = errors.New("creator err") - - err = v.Assert(e) - require.ErrorIs(t, err, e.creatorErr) - - e.creatorErr = nil - - // read info error - e.infoErr = errors.New("info err") - - err = v.Assert(e) - require.ErrorIs(t, err, e.infoErr) - - e.infoErr = nil - - // diff explicit ID and the one in info - var id2 subnetid.ID - - id2.SetNumeric(idNum + 1) - - e.info.SetID(id2) - - err = v.Assert(e) - require.ErrorIs(t, err, errDiffID) - - e.info.SetID(e.id) - - // diff explicit creator and the one in info - creator2 := *usertest.ID(t) - - e.info.SetOwner(creator2) - - err = v.Assert(e) - require.ErrorIs(t, err, errDiffOwner) - - e.info.SetOwner(e.creator) - - err = v.Assert(e) - require.NoError(t, err) -} diff --git a/pkg/innerring/subnet.go b/pkg/innerring/subnet.go deleted file mode 100644 index 1d68df59f0..0000000000 --- a/pkg/innerring/subnet.go +++ /dev/null @@ -1,313 +0,0 @@ -package innerring - -import ( - "errors" - "fmt" - - "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" - neogoutil "github.com/nspcc-dev/neo-go/pkg/util" - irsubnet "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/subnet" - netmapclient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" - morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet" - "github.com/nspcc-dev/neofs-node/pkg/morph/event" - subnetevents "github.com/nspcc-dev/neofs-node/pkg/morph/event/subnet" - "github.com/nspcc-dev/neofs-node/pkg/util" - "github.com/nspcc-dev/neofs-sdk-go/netmap" - "github.com/nspcc-dev/neofs-sdk-go/subnet" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" - "github.com/nspcc-dev/neofs-sdk-go/user" - "github.com/panjf2000/ants/v2" - "go.uber.org/zap" -) - -// IR server's component to handle Subnet contract notifications. -type subnetHandler struct { - workerPool util.WorkerPool - - morphClient morphsubnet.Client - - putValidator irsubnet.PutValidator -} - -// configuration of subnet component. -type subnetConfig struct { - queueSize uint32 -} - -// makes IR server to catch Subnet notifications from the sidechain listener, -// and to release the corresponding processing queue on stop. -func (s *Server) initSubnet(cfg subnetConfig) { - s.registerStarter(func() error { - var err error - - // initialize queue for processing of the events from Subnet contract - s.subnetHandler.workerPool, err = ants.NewPool(int(cfg.queueSize), ants.WithNonblocking(true)) - if err != nil { - return fmt.Errorf("subnet queue initialization: %w", err) - } - - // initialize morph client of Subnet contract - clientMode := morphsubnet.NotaryAlphabet - - var initPrm morphsubnet.InitPrm - - initPrm.SetBaseClient(s.morphClient) - initPrm.SetContractAddress(s.contracts.subnet) - initPrm.SetMode(clientMode) - - err = s.subnetHandler.morphClient.Init(initPrm) - if err != nil { - return fmt.Errorf("init morph subnet client: %w", err) - } - - s.listenSubnet() - - return nil - }) - - s.registerCloser(func() error { - s.stopSubnet() - return nil - }) -} - -// releases the Subnet contract notification processing queue. -func (s *Server) stopSubnet() { - s.workerPool.Release() -} - -// names of listened notification events from Subnet contract. -const ( - subnetRemoveEvName = "Delete" - notarySubnetCreateEvName = "put" -) - -// makes the IR server to listen to notifications of Subnet contract. -// All required resources must be initialized before (initSubnet). -// It works in one of two modes (configured): notary and non-notary. -// -// All handlers are executed only if the local node is an alphabet one. -// -// Events (notary): -// - put (parser: subnetevents.ParseNotaryPut, handler: catchSubnetCreation); -// - Delete (parser: subnetevents.ParseDelete, handler: catchSubnetCreation). -// -// Events (non-notary): -// - Put (parser: subnetevents.ParsePut, handler: catchSubnetCreation); -// - Delete (parser: subnetevents.ParseDelete, handler: catchSubnetCreation). -func (s *Server) listenSubnet() { - var ( - parserInfo event.NotaryParserInfo - handlerInfo event.NotaryHandlerInfo - ) - - parserInfo.SetScriptHash(s.contracts.subnet) - handlerInfo.SetScriptHash(s.contracts.subnet) - - listenNotaryEvent := func(notifyName string, parser event.NotaryParser, handler event.Handler) { - notifyTyp := event.NotaryTypeFromString(notifyName) - - parserInfo.SetMempoolType(mempoolevent.TransactionAdded) - handlerInfo.SetMempoolType(mempoolevent.TransactionAdded) - - parserInfo.SetParser(parser) - handlerInfo.SetHandler(handler) - - parserInfo.SetRequestType(notifyTyp) - handlerInfo.SetRequestType(notifyTyp) - - s.morphListener.SetNotaryParser(parserInfo) - s.morphListener.RegisterNotaryHandler(handlerInfo) - } - - // subnet creation - listenNotaryEvent(notarySubnetCreateEvName, subnetevents.ParseNotaryPut, s.onlyAlphabetEventHandler(s.catchSubnetCreation)) - // subnet removal - listenNotifySubnetEvent(s, subnetRemoveEvName, subnetevents.ParseDelete, s.onlyAlphabetEventHandler(s.catchSubnetRemoval)) -} - -func listenNotifySubnetEvent(s *Server, notifyName string, parser event.NotificationParser, handler event.Handler) { - var ( - parserInfo event.NotificationParserInfo - handlerInfo event.NotificationHandlerInfo - ) - - parserInfo.SetScriptHash(s.contracts.subnet) - handlerInfo.SetScriptHash(s.contracts.subnet) - - notifyTyp := event.TypeFromString(notifyName) - - parserInfo.SetType(notifyTyp) - handlerInfo.SetType(notifyTyp) - - parserInfo.SetParser(parser) - handlerInfo.SetHandler(handler) - - s.morphListener.SetNotificationParser(parserInfo) - s.morphListener.RegisterNotificationHandler(handlerInfo) -} - -// catchSubnetCreation catches event of subnet creation from listener and queues the processing. -func (s *Server) catchSubnetCreation(e event.Event) { - err := s.subnetHandler.workerPool.Submit(func() { - s.handleSubnetCreation(e) - }) - if err != nil { - s.log.Error("subnet creation queue failure", - zap.String("error", err.Error()), - ) - } -} - -// implements irsubnet.Put event interface required by irsubnet.PutValidator. -type putSubnetEvent struct { - ev subnetevents.Put -} - -// ReadID unmarshals the subnet ID from a binary NeoFS API protocol's format. -func (x putSubnetEvent) ReadID(id *subnetid.ID) error { - return id.Unmarshal(x.ev.ID()) -} - -var errMissingSubnetOwner = errors.New("missing subnet owner") - -// ReadCreator unmarshals the subnet creator from a binary NeoFS API protocol's format. -// Returns an error if the byte array is empty. -func (x putSubnetEvent) ReadCreator(id *user.ID) error { - data := x.ev.Owner() - - if len(data) == 0 { - return errMissingSubnetOwner - } - - return user.IDFromKey(id, data) -} - -// ReadInfo unmarshal the subnet info from a binary NeoFS API protocol's format. -func (x putSubnetEvent) ReadInfo(info *subnet.Info) error { - return info.Unmarshal(x.ev.Info()) -} - -// handleSubnetCreation handles an event of subnet creation parsed via subnetevents.ParsePut. -// -// Validates the event using irsubnet.PutValidator. Logs message about (dis)agreement. -func (s *Server) handleSubnetCreation(e event.Event) { - putEv := e.(subnetevents.Put) // panic occurs only if we registered handler incorrectly - - err := s.subnetHandler.putValidator.Assert(putSubnetEvent{ - ev: putEv, - }) - if err != nil { - s.log.Info("discard subnet creation", - zap.String("reason", err.Error()), - ) - - return - } - - notaryMainTx := putEv.NotaryMainTx() - - // re-sign notary request - err = s.morphClient.NotarySignAndInvokeTX(notaryMainTx) - - if err != nil { - s.log.Error("approve subnet creation", - zap.String("error", err.Error()), - ) - - return - } -} - -// catchSubnetRemoval catches an event of subnet removal from listener and queues the processing. -func (s *Server) catchSubnetRemoval(e event.Event) { - err := s.subnetHandler.workerPool.Submit(func() { - s.handleSubnetRemoval(e) - }) - if err != nil { - s.log.Error("subnet removal handling failure", - zap.String("error", err.Error()), - ) - } -} - -// handleSubnetRemoval handles event of subnet removal parsed via subnetevents.ParseDelete. -func (s *Server) handleSubnetRemoval(e event.Event) { - delEv := e.(subnetevents.Delete) // panic occurs only if we registered handler incorrectly - - // handle subnet changes in netmap - - candidates, err := s.netmapClient.GetCandidates() - if err != nil { - s.log.Error("getting netmap candidates", - zap.Error(err), - ) - - return - } - - var removedID subnetid.ID - err = removedID.Unmarshal(delEv.ID()) - if err != nil { - s.log.Error("unmarshalling removed subnet ID", - zap.String("error", err.Error()), - ) - - return - } - - for i := range candidates { - s.processCandidate(delEv.TxHash(), removedID, candidates[i]) - } -} - -func (s *Server) processCandidate(txHash neogoutil.Uint256, removedID subnetid.ID, c netmap.NodeInfo) { - removeSubnet := false - log := s.log.With( - zap.String("public_key", netmap.StringifyPublicKey(c)), - zap.String("removed_subnet", removedID.String()), - ) - - err := c.IterateSubnets(func(id subnetid.ID) error { - if removedID.Equals(id) { - removeSubnet = true - return netmap.ErrRemoveSubnet - } - - return nil - }) - if err != nil { - log.Error("iterating node's subnets", zap.Error(err)) - log.Debug("removing node from netmap candidates") - - var updateStatePrm netmapclient.UpdatePeerPrm - updateStatePrm.SetKey(c.PublicKey()) - updateStatePrm.SetHash(txHash) - - err = s.netmapClient.UpdatePeerState(updateStatePrm) - if err != nil { - log.Error("removing node from candidates", - zap.Error(err), - ) - } - - return - } - - // remove subnet from node's information - // if it contains removed subnet - if removeSubnet { - log.Debug("removing subnet from the node") - - var addPeerPrm netmapclient.AddPeerPrm - addPeerPrm.SetNodeInfo(c) - addPeerPrm.SetHash(txHash) - - err = s.netmapClient.AddPeer(addPeerPrm) - if err != nil { - log.Error("updating subnet info", - zap.Error(err), - ) - } - } -} diff --git a/pkg/metrics/innerring.go b/pkg/metrics/innerring.go index 201efac659..9492c29170 100644 --- a/pkg/metrics/innerring.go +++ b/pkg/metrics/innerring.go @@ -1,21 +1,14 @@ package metrics import ( - "fmt" - "github.com/prometheus/client_golang/prometheus" ) const innerRingNameSpace = "neofs_ir" -// FIXME: drop after v0.38.0 release: #2347. -const innerRingNameSpaceDeprecated = storageNodeNameSpace -const innerRingSubsystemDeprecated = objectSubsystem - // InnerRingServiceMetrics contains metrics collected by inner ring. type InnerRingServiceMetrics struct { - epoch prometheus.Gauge - epochDeprecated prometheus.Gauge + epoch prometheus.Gauge } // NewInnerRingMetrics returns new instance of metrics collectors for inner ring. @@ -30,22 +23,12 @@ func NewInnerRingMetrics(version string) InnerRingServiceMetrics { }) prometheus.MustRegister(epoch) - epochDeprecated := prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: innerRingNameSpaceDeprecated, - Subsystem: innerRingSubsystemDeprecated, - Name: "epoch", - Help: fmt.Sprintf("Current epoch as seen by inner-ring node. DEPRECATED: use [%s_%s_epoch] instead.", innerRingNameSpace, stateSubsystem), - }) - prometheus.MustRegister(epochDeprecated) - return InnerRingServiceMetrics{ - epoch: epoch, - epochDeprecated: epochDeprecated, + epoch: epoch, } } // SetEpoch updates epoch metrics. func (m InnerRingServiceMetrics) SetEpoch(epoch uint64) { m.epoch.Set(float64(epoch)) - m.epochDeprecated.Set(float64(epoch)) } diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index debe68eadf..14275d6cdf 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -1,8 +1,6 @@ package metrics import ( - "fmt" - "github.com/prometheus/client_golang/prometheus" ) @@ -12,8 +10,7 @@ type NodeMetrics struct { objectServiceMetrics engineMetrics stateMetrics - epoch prometheus.Gauge - epochDeprecated prometheus.Gauge + epoch prometheus.Gauge } func NewNodeMetrics(version string) *NodeMetrics { @@ -36,27 +33,15 @@ func NewNodeMetrics(version string) *NodeMetrics { }) prometheus.MustRegister(epoch) - // FIXME: drop after v0.38.0 release: #2347. - const stateSubsystemDeprecated = objectSubsystem - epochDeprecated := prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: storageNodeNameSpace, - Subsystem: stateSubsystemDeprecated, - Name: "epoch", - Help: fmt.Sprintf("Current epoch as seen by inner-ring node. DEPRECATED: use [%s_%s_epoch] instead.", storageNodeNameSpace, stateSubsystem), - }) - prometheus.MustRegister(epochDeprecated) - return &NodeMetrics{ objectServiceMetrics: objectService, engineMetrics: engine, stateMetrics: state, epoch: epoch, - epochDeprecated: epochDeprecated, } } // SetEpoch updates epoch metric. func (m *NodeMetrics) SetEpoch(epoch uint64) { m.epoch.Set(float64(epoch)) - m.epochDeprecated.Set(float64(epoch)) } diff --git a/pkg/morph/client/constructor.go b/pkg/morph/client/constructor.go index 3b5919adcb..03638a17dd 100644 --- a/pkg/morph/client/constructor.go +++ b/pkg/morph/client/constructor.go @@ -42,6 +42,7 @@ type cfg struct { singleCli *rpcclient.WSClient // neo-go client for single client mode inactiveModeCb Callback + rpcSwitchCb Callback reconnectionRetries int reconnectionDelay time.Duration @@ -297,3 +298,13 @@ func WithConnLostCallback(cb Callback) Option { c.inactiveModeCb = cb } } + +// WithConnSwitchCallback returns a client constructor option +// that specifies a callback that is called when the Client +// reconnected to a new RPC (from [WithEndpoints] list) +// successfully. +func WithConnSwitchCallback(cb Callback) Option { + return func(c *cfg) { + c.rpcSwitchCb = cb + } +} diff --git a/pkg/morph/client/multi.go b/pkg/morph/client/multi.go index 1b5c21a56c..8b7b7aa155 100644 --- a/pkg/morph/client/multi.go +++ b/pkg/morph/client/multi.go @@ -15,21 +15,28 @@ type Endpoint struct { // SwitchRPC performs reconnection and returns true if it was successful. func (c *Client) SwitchRPC() bool { c.switchLock.Lock() - defer c.switchLock.Unlock() for attempt := 0; attempt < c.cfg.reconnectionRetries; attempt++ { if c.switchPRC() { + c.switchLock.Unlock() + + if c.cfg.rpcSwitchCb != nil { + c.cfg.rpcSwitchCb() + } + return true } select { case <-time.After(c.cfg.reconnectionDelay): case <-c.closeChan: + c.switchLock.Unlock() return false } } c.inactive = true + c.switchLock.Unlock() if c.cfg.inactiveModeCb != nil { c.cfg.inactiveModeCb() diff --git a/pkg/morph/client/nns.go b/pkg/morph/client/nns.go index a987817f2b..849b9f996a 100644 --- a/pkg/morph/client/nns.go +++ b/pkg/morph/client/nns.go @@ -34,8 +34,6 @@ const ( NNSProxyContractName = "proxy.neofs" // NNSReputationContractName is a name of the reputation contract in NNS. NNSReputationContractName = "reputation.neofs" - // NNSSubnetworkContractName is a name of the subnet contract in NNS. - NNSSubnetworkContractName = "subnet.neofs" // NNSGroupKeyName is a name for the NeoFS group key record in NNS. NNSGroupKeyName = "group.neofs" ) diff --git a/pkg/morph/client/subnet/admin.go b/pkg/morph/client/subnet/admin.go deleted file mode 100644 index 832c40a0df..0000000000 --- a/pkg/morph/client/subnet/admin.go +++ /dev/null @@ -1,87 +0,0 @@ -package morphsubnet - -import "github.com/nspcc-dev/neofs-node/pkg/morph/client" - -// ManageAdminsPrm groups parameters of administer methods of Subnet contract. -// -// Zero value adds node admin. Subnet, key and group must be specified via setters. -type ManageAdminsPrm struct { - // remove or add admin - rm bool - - // client or node admin - client bool - - subnet []byte - - admin []byte - - group []byte -} - -// SetRemove marks admin to be removed. By default, admin is added. -func (x *ManageAdminsPrm) SetRemove() { - x.rm = true -} - -// SetClient switches to client admin. By default, node admin is modified. -func (x *ManageAdminsPrm) SetClient() { - x.client = true -} - -// SetSubnet sets identifier of the subnet in a binary NeoFS API protocol format. -func (x *ManageAdminsPrm) SetSubnet(id []byte) { - x.subnet = id -} - -// SetAdmin sets admin's public key in a binary format. -func (x *ManageAdminsPrm) SetAdmin(key []byte) { - x.admin = key -} - -// SetGroup sets identifier of the client group in a binary NeoFS API protocol format. -// Makes sense only for client admins (see ManageAdminsPrm.SetClient). -func (x *ManageAdminsPrm) SetGroup(id []byte) { - x.group = id -} - -// ManageAdminsRes groups the resulting values of node administer methods of Subnet contract. -type ManageAdminsRes struct{} - -// ManageAdmins manages admin list of the NeoFS subnet through Subnet contract calls. -func (x Client) ManageAdmins(prm ManageAdminsPrm) (*ManageAdminsPrm, error) { - var method string - - args := make([]interface{}, 1, 3) - args[0] = prm.subnet - - if prm.client { - args = append(args, prm.group, prm.admin) - - if prm.rm { - method = removeClientAdminMethod - } else { - method = addClientAdminMethod - } - } else { - args = append(args, prm.admin) - - if prm.rm { - method = removeNodeAdminMethod - } else { - method = addNodeAdminMethod - } - } - - var prmInvoke client.InvokePrm - - prmInvoke.SetMethod(method) - prmInvoke.SetArgs(args...) - - err := x.client.Invoke(prmInvoke) - if err != nil { - return nil, err - } - - return new(ManageAdminsPrm), nil -} diff --git a/pkg/morph/client/subnet/client.go b/pkg/morph/client/subnet/client.go deleted file mode 100644 index 784a81d367..0000000000 --- a/pkg/morph/client/subnet/client.go +++ /dev/null @@ -1,104 +0,0 @@ -package morphsubnet - -import ( - "fmt" - - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neofs-node/pkg/morph/client" -) - -// Client represents Subnet contract client. -// -// Client should be preliminary initialized (see Init method). -type Client struct { - client *client.StaticClient -} - -// InitPrm groups parameters of Client's initialization. -type InitPrm struct { - base *client.Client - - addr util.Uint160 - - modeSet bool - mode Mode -} - -const ( - deleteMethod = "delete" - getMethod = "get" - putMethod = "put" - - removeClientAdminMethod = "removeClientAdmin" - addClientAdminMethod = "addClientAdmin" - - userAllowedMethod = "userAllowed" - removeUserMethod = "removeUser" - addUserMethod = "addUser" - - removeNodeAdminMethod = "removeNodeAdmin" - addNodeAdminMethod = "addNodeAdmin" - nodeAllowedMethod = "nodeAllowed" - removeNodeMethod = "removeNode" - addNodeMethod = "addNode" -) - -// SetBaseClient sets basic morph client. -func (x *InitPrm) SetBaseClient(base *client.Client) { - x.base = base -} - -// SetContractAddress sets address of Subnet contract in NeoFS sidechain. -func (x *InitPrm) SetContractAddress(addr util.Uint160) { - x.addr = addr -} - -// Mode regulates client work mode. -type Mode uint8 - -const ( - _ Mode = iota - - // NotaryAlphabet makes client to use its internal key for signing the notary requests. - NotaryAlphabet - - // NotaryNonAlphabet makes client to not use its internal key for signing the notary requests. - NotaryNonAlphabet -) - -// SetMode makes client to work with non-notary sidechain. -// By default, NonNotary is used. -func (x *InitPrm) SetMode(mode Mode) { - x.modeSet = true - x.mode = mode -} - -// Init initializes client with specified parameters. -// -// Base client must be set. -func (x *Client) Init(prm InitPrm) error { - if prm.base == nil { - panic("missing base morph client") - } - - if !prm.modeSet { - prm.mode = NotaryNonAlphabet - } - - var opts []client.StaticClientOption - - switch prm.mode { - default: - panic(fmt.Sprintf("invalid work mode %d", prm.mode)) - case NotaryNonAlphabet: - opts = []client.StaticClientOption{client.TryNotary()} - case NotaryAlphabet: - opts = []client.StaticClientOption{client.TryNotary(), client.AsAlphabet()} - } - - var err error - - x.client, err = client.NewStatic(prm.base, prm.addr, 0, opts...) - - return err -} diff --git a/pkg/morph/client/subnet/clients.go b/pkg/morph/client/subnet/clients.go deleted file mode 100644 index 3d0ed4ed05..0000000000 --- a/pkg/morph/client/subnet/clients.go +++ /dev/null @@ -1,114 +0,0 @@ -package morphsubnet - -import ( - "fmt" - - "github.com/nspcc-dev/neofs-node/pkg/morph/client" -) - -// UserAllowedPrm groups parameters of UserAllowed method of Subnet contract. -type UserAllowedPrm struct { - args [2]interface{} -} - -// SetID sets identifier of the subnet in a binary NeoFS API protocol format. -func (x *UserAllowedPrm) SetID(id []byte) { - x.args[0] = id -} - -// SetClient sets owner ID of the client that is being checked in a binary NeoFS API protocol format. -func (x *UserAllowedPrm) SetClient(id []byte) { - x.args[1] = id -} - -// UserAllowedRes groups the resulting values of UserAllowed method of Subnet contract. -type UserAllowedRes struct { - result bool -} - -// Allowed returns true iff the client is allowed to create containers in the subnet. -func (x UserAllowedRes) Allowed() bool { - return x.result -} - -// UserAllowed checks if the user has access to the subnetwork. -func (x *Client) UserAllowed(prm UserAllowedPrm) (*UserAllowedRes, error) { - args := client.TestInvokePrm{} - - args.SetMethod(userAllowedMethod) - args.SetArgs(prm.args[:]...) - - res, err := x.client.TestInvoke(args) - if err != nil { - return nil, fmt.Errorf("could not make test invoke: %w", err) - } - - if len(res) == 0 { - return nil, errEmptyResponse - } - - result, err := client.BoolFromStackItem(res[0]) - if err != nil { - return nil, err - } - - return &UserAllowedRes{ - result: result, - }, nil -} - -// ManageClientsPrm groups parameters of client management in Subnet contract. -// -// Zero value adds subnet client. Subnet, group and client ID must be specified via setters. -type ManageClientsPrm struct { - // remove or add client - rm bool - - args [3]interface{} -} - -// SetRemove marks client to be removed. By default, client is added. -func (x *ManageClientsPrm) SetRemove() { - x.rm = true -} - -// SetSubnet sets identifier of the subnet in a binary NeoFS API protocol format. -func (x *ManageClientsPrm) SetSubnet(id []byte) { - x.args[0] = id -} - -// SetGroup sets identifier of the client group in a binary NeoFS API protocol format. -func (x *ManageClientsPrm) SetGroup(id []byte) { - x.args[1] = id -} - -// SetClient sets client's user ID in a binary NeoFS API protocol format. -func (x *ManageClientsPrm) SetClient(id []byte) { - x.args[2] = id -} - -// ManageClientsRes groups the resulting values of client management methods of Subnet contract. -type ManageClientsRes struct{} - -// ManageClients manages client list of the NeoFS subnet through Subnet contract calls. -func (x Client) ManageClients(prm ManageClientsPrm) (*ManageClientsRes, error) { - var method string - - if prm.rm { - method = removeUserMethod - } else { - method = addUserMethod - } - - var prmInvoke client.InvokePrm - - prmInvoke.SetMethod(method) - prmInvoke.SetArgs(prm.args[:]...) - - err := x.client.Invoke(prmInvoke) - if err != nil { - return nil, err - } - - return new(ManageClientsRes), nil -} diff --git a/pkg/morph/client/subnet/delete.go b/pkg/morph/client/subnet/delete.go deleted file mode 100644 index 6fed72f9f5..0000000000 --- a/pkg/morph/client/subnet/delete.go +++ /dev/null @@ -1,40 +0,0 @@ -package morphsubnet - -import ( - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neofs-node/pkg/morph/client" -) - -// DeletePrm groups parameters of Delete method of Subnet contract. -type DeletePrm struct { - cliPrm client.InvokePrm - - args [1]interface{} -} - -// SetTxHash sets hash of the transaction which spawned the notification. -// Ignore this parameter for new requests. -func (x *DeletePrm) SetTxHash(hash util.Uint256) { - x.cliPrm.SetHash(hash) -} - -// SetID sets identifier of the subnet to be removed in a binary NeoFS API protocol format. -func (x *DeletePrm) SetID(id []byte) { - x.args[0] = id -} - -// DeleteRes groups the resulting values of Delete method of Subnet contract. -type DeleteRes struct{} - -// Delete removes subnet though the call of the corresponding method of the Subnet contract. -func (x Client) Delete(prm DeletePrm) (*DeleteRes, error) { - prm.cliPrm.SetMethod(deleteMethod) - prm.cliPrm.SetArgs(prm.args[:]...) - - err := x.client.Invoke(prm.cliPrm) - if err != nil { - return nil, err - } - - return new(DeleteRes), nil -} diff --git a/pkg/morph/client/subnet/get.go b/pkg/morph/client/subnet/get.go deleted file mode 100644 index 7fcefc760c..0000000000 --- a/pkg/morph/client/subnet/get.go +++ /dev/null @@ -1,55 +0,0 @@ -package morphsubnet - -import ( - "errors" - - "github.com/nspcc-dev/neofs-node/pkg/morph/client" -) - -// GetPrm groups parameters of Get method of Subnet contract. -type GetPrm struct { - args [1]interface{} -} - -// SetID sets identifier of the subnet to be read in a binary NeoFS API protocol format. -func (x *GetPrm) SetID(id []byte) { - x.args[0] = id -} - -// GetRes groups the resulting values of Get method of Subnet contract. -type GetRes struct { - info []byte -} - -// Info returns information about the subnet in a binary format of NeoFS API protocol. -func (x GetRes) Info() []byte { - return x.info -} - -var errEmptyResponse = errors.New("empty response") - -// Get reads the subnet through the call of the corresponding method of the Subnet contract. -func (x *Client) Get(prm GetPrm) (*GetRes, error) { - var prmGet client.TestInvokePrm - - prmGet.SetMethod(getMethod) - prmGet.SetArgs(prm.args[:]...) - - res, err := x.client.TestInvoke(prmGet) - if err != nil { - return nil, err - } - - if len(res) == 0 { - return nil, errEmptyResponse - } - - data, err := client.BytesFromStackItem(res[0]) - if err != nil { - return nil, err - } - - return &GetRes{ - info: data, - }, nil -} diff --git a/pkg/morph/client/subnet/node.go b/pkg/morph/client/subnet/node.go deleted file mode 100644 index 14afbd47ce..0000000000 --- a/pkg/morph/client/subnet/node.go +++ /dev/null @@ -1,58 +0,0 @@ -package morphsubnet - -import ( - "fmt" - - "github.com/nspcc-dev/neofs-node/pkg/morph/client" -) - -// NodeAllowedPrm groups parameters of NodeAllowed method of Subnet contract. -type NodeAllowedPrm struct { - cliPrm client.TestInvokePrm - - args [2]interface{} -} - -// SetID sets identifier of the subnet of the node in a binary NeoFS API protocol format. -func (x *NodeAllowedPrm) SetID(id []byte) { - x.args[0] = id -} - -// SetNode sets public key of the node that is being checked. -func (x *NodeAllowedPrm) SetNode(id []byte) { - x.args[1] = id -} - -// NodeAllowedRes groups the resulting values of NodeAllowed method of Subnet contract. -type NodeAllowedRes struct { - result bool -} - -// Allowed returns true iff the node is allowed to enter the subnet. -func (x NodeAllowedRes) Allowed() bool { - return x.result -} - -// NodeAllowed checks if the node is included in the subnetwork. -func (x *Client) NodeAllowed(prm NodeAllowedPrm) (*NodeAllowedRes, error) { - prm.cliPrm.SetMethod(nodeAllowedMethod) - prm.cliPrm.SetArgs(prm.args[:]...) - - res, err := x.client.TestInvoke(prm.cliPrm) - if err != nil { - return nil, fmt.Errorf("could not make test invoke: %w", err) - } - - if len(res) == 0 { - return nil, errEmptyResponse - } - - result, err := client.BoolFromStackItem(res[0]) - if err != nil { - return nil, err - } - - return &NodeAllowedRes{ - result: result, - }, nil -} diff --git a/pkg/morph/client/subnet/nodes.go b/pkg/morph/client/subnet/nodes.go deleted file mode 100644 index 79e8463790..0000000000 --- a/pkg/morph/client/subnet/nodes.go +++ /dev/null @@ -1,54 +0,0 @@ -package morphsubnet - -import "github.com/nspcc-dev/neofs-node/pkg/morph/client" - -// ManageNodesPrm groups parameters of node management in Subnet contract. -// -// Zero value adds node to subnet. Subnet and node IDs must be specified via setters. -type ManageNodesPrm struct { - // remove or add node - rm bool - - args [2]interface{} -} - -// SetRemove marks node to be removed. By default, node is added. -func (x *ManageNodesPrm) SetRemove() { - x.rm = true -} - -// SetSubnet sets identifier of the subnet in a binary NeoFS API protocol format. -func (x *ManageNodesPrm) SetSubnet(id []byte) { - x.args[0] = id -} - -// SetNode sets node's public key in a binary format. -func (x *ManageNodesPrm) SetNode(id []byte) { - x.args[1] = id -} - -// ManageNodesRes groups the resulting values of node management methods of Subnet contract. -type ManageNodesRes struct{} - -// ManageNodes manages node list of the NeoFS subnet through Subnet contract calls. -func (x Client) ManageNodes(prm ManageNodesPrm) (*ManageNodesRes, error) { - var method string - - if prm.rm { - method = removeNodeMethod - } else { - method = addNodeMethod - } - - var prmInvoke client.InvokePrm - - prmInvoke.SetMethod(method) - prmInvoke.SetArgs(prm.args[:]...) - - err := x.client.Invoke(prmInvoke) - if err != nil { - return nil, err - } - - return new(ManageNodesRes), nil -} diff --git a/pkg/morph/client/subnet/put.go b/pkg/morph/client/subnet/put.go deleted file mode 100644 index d2736613a7..0000000000 --- a/pkg/morph/client/subnet/put.go +++ /dev/null @@ -1,50 +0,0 @@ -package morphsubnet - -import ( - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neofs-node/pkg/morph/client" -) - -// PutPrm groups parameters of Put method of Subnet contract. -type PutPrm struct { - cliPrm client.InvokePrm - - args [3]interface{} -} - -// SetTxHash sets hash of the transaction which spawned the notification. -// Ignore this parameter for new requests. -func (x *PutPrm) SetTxHash(hash util.Uint256) { - x.cliPrm.SetHash(hash) -} - -// SetID sets identifier of the created subnet in a binary NeoFS API protocol format. -func (x *PutPrm) SetID(id []byte) { - x.args[0] = id -} - -// SetOwner sets identifier of the subnet owner in a binary NeoFS API protocol format. -func (x *PutPrm) SetOwner(id []byte) { - x.args[1] = id -} - -// SetInfo sets information about the created subnet in a binary NeoFS API protocol format. -func (x *PutPrm) SetInfo(id []byte) { - x.args[2] = id -} - -// PutRes groups the resulting values of Put method of Subnet contract. -type PutRes struct{} - -// Put creates subnet though the call of the corresponding method of the Subnet contract. -func (x Client) Put(prm PutPrm) (*PutRes, error) { - prm.cliPrm.SetMethod(putMethod) - prm.cliPrm.SetArgs(prm.args[:]...) - - err := x.client.Invoke(prm.cliPrm) - if err != nil { - return nil, err - } - - return new(PutRes), nil -} diff --git a/pkg/morph/deploy/deploy.go b/pkg/morph/deploy/deploy.go new file mode 100644 index 0000000000..20a55a35a9 --- /dev/null +++ b/pkg/morph/deploy/deploy.go @@ -0,0 +1,205 @@ +// Package deploy provides NeoFS Sidechain deployment functionality. +package deploy + +import ( + "context" + "errors" + "fmt" + "sort" + + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "go.uber.org/zap" +) + +// Blockchain groups services provided by particular Neo blockchain network +// representing NeoFS Sidechain that are required for its deployment. +type Blockchain interface { + // RPCActor groups functions needed to compose and send transactions to the + // blockchain. + actor.RPCActor + + // GetCommittee returns list of public keys owned by Neo blockchain committee + // members. Resulting list is non-empty, unique and unsorted. + GetCommittee() (keys.PublicKeys, error) + + // GetContractStateByID returns network state of the smart contract by its ID. + // GetContractStateByID returns error with 'Unknown contract' substring if + // requested contract is missing. + GetContractStateByID(id int32) (*state.Contract, error) + + // ReceiveBlocks starts background process that forwards new blocks of the + // blockchain to the provided channel. The process handles all new blocks when + // ReceiveBlocks is called with nil filter. Returns unique identifier to be used + // to stop the process via Unsubscribe. + ReceiveBlocks(*neorpc.BlockFilter, chan<- *block.Block) (id string, err error) + + // Unsubscribe stops background process started by ReceiveBlocks by ID. + Unsubscribe(id string) error +} + +// KeyStorage represents storage of the private keys. +type KeyStorage interface { + // GetPersistedPrivateKey returns singleton private key persisted in the + // storage. GetPersistedPrivateKey randomizes the key initially. All subsequent + // successful calls return the same key. + GetPersistedPrivateKey() (*keys.PrivateKey, error) +} + +// CommonDeployPrm groups common deployment parameters of the smart contract. +type CommonDeployPrm struct { + NEF nef.File + Manifest manifest.Manifest +} + +// NNSPrm groups deployment parameters of the NeoFS NNS contract. +type NNSPrm struct { + Common CommonDeployPrm + SystemEmail string +} + +// Prm groups all parameters of the NeoFS Sidechain deployment procedure. +type Prm struct { + // Writes progress into the log. + Logger *zap.Logger + + // Particular Neo blockchain instance to be used as NeoFS Sidechain. + Blockchain Blockchain + + // Local process account used for transaction signing (must be unlocked). + LocalAccount *wallet.Account + + // Storage for single committee group key. + KeyStorage KeyStorage + + NNS NNSPrm +} + +// Deploy initializes Neo network represented by given Prm.Blockchain as NeoFS +// Sidechain and makes it full-featured for NeoFS storage system operation. +// +// Deploy aborts only by context or when a fatal error occurs. Deployment +// progress is logged in detail. It is expected that some situations can be +// changed/fixed on the chain from the outside, so Deploy adapts flexibly and +// does not stop at the moment. +// +// Deployment process is detailed in NeoFS docs. Summary of stages: +// 1. NNS contract deployment +// 2. launch of a notary service for the committee +// 3. committee group initialization +// 4. deployment of the NeoFS system contracts (currently not done) +// 5. deployment of custom contracts +// +// See project documentation for details. +func Deploy(ctx context.Context, prm Prm) error { + committee, err := prm.Blockchain.GetCommittee() + if err != nil { + return fmt.Errorf("get Neo committee of the network: %w", err) + } + + sort.Sort(committee) + + // determine a leader + localPrivateKey := prm.LocalAccount.PrivateKey() + localPublicKey := localPrivateKey.PublicKey() + localAccCommitteeIndex := -1 + + for i := range committee { + if committee[i].Equal(localPublicKey) { + localAccCommitteeIndex = i + break + } + } + + if localAccCommitteeIndex < 0 { + return errors.New("local account does not belong to any Neo committee member") + } + + deployNNSPrm := deployNNSContractPrm{ + logger: prm.Logger, + blockchain: prm.Blockchain, + localAcc: prm.LocalAccount, + localNEF: prm.NNS.Common.NEF, + localManifest: prm.NNS.Common.Manifest, + systemEmail: prm.NNS.SystemEmail, + initCommitteeGroupKey: nil, // set below + } + + // if local node is the first committee member (Az) => deploy NNS contract, + // otherwise just wait + if localAccCommitteeIndex == 0 { + // Why such a centralized approach? There is a need to initialize committee + // contract group and share its private key between all committee members (the + // latter is done in the current procedure next). Currently, there is no + // convenient Neo service for this, and we don't want to use anything but + // blockchain, so the key is distributed through domain NNS records. However, + // then the chicken-and-egg problem pops up: committee group must be also set + // for the NNS contract. To set the group, you need to know the contract hash in + // advance, and it is a function from the sender of the deployment transaction. + // Summing up all these statements, we come to the conclusion that the one who + // deploys the contract creates the group key, and he shares it among the other + // members. Technically any committee member could deploy NNS contract, but for + // the sake of simplicity, this is a fixed node. This makes the procedure even + // more centralized, however, in practice, at the start of the network, all + // members are expected to be healthy and active. + // + // Note that manifest can't be changed w/o NEF change, so it's impossible to set + // committee group dynamically right after deployment. See + // https://github.com/nspcc-dev/neofs-contract/issues/340 + deployNNSPrm.initCommitteeGroupKey = prm.KeyStorage.GetPersistedPrivateKey + } + + prm.Logger.Info("initializing NNS contract on the chain...") + + nnsOnChainAddress, err := initNNSContract(ctx, deployNNSPrm) + if err != nil { + return fmt.Errorf("init NNS contract on the chain: %w", err) + } + + prm.Logger.Info("NNS contract successfully initialized on the chain", zap.Stringer("address", nnsOnChainAddress)) + + prm.Logger.Info("enable Notary service for the committee...") + + err = enableNotary(ctx, enableNotaryPrm{ + logger: prm.Logger, + blockchain: prm.Blockchain, + nnsOnChainAddress: nnsOnChainAddress, + systemEmail: prm.NNS.SystemEmail, + committee: committee, + localAcc: prm.LocalAccount, + localAccCommitteeIndex: localAccCommitteeIndex, + }) + if err != nil { + return fmt.Errorf("enable Notary service for the committee: %w", err) + } + + prm.Logger.Info("Notary service successfully enabled for the committee") + + prm.Logger.Info("initializing committee group for contract management...") + + committeeGroupKey, err := initCommitteeGroup(ctx, initCommitteeGroupPrm{ + logger: prm.Logger, + blockchain: prm.Blockchain, + nnsOnChainAddress: nnsOnChainAddress, + systemEmail: prm.NNS.SystemEmail, + committee: committee, + localAcc: prm.LocalAccount, + localAccCommitteeIndex: localAccCommitteeIndex, + keyStorage: prm.KeyStorage, + }) + if err != nil { + return fmt.Errorf("init committee group: %w", err) + } + + prm.Logger.Info("committee group successfully initialized", zap.Stringer("public key", committeeGroupKey.PublicKey())) + + // TODO: deploy contracts + + return nil +} diff --git a/pkg/morph/deploy/group.go b/pkg/morph/deploy/group.go new file mode 100644 index 0000000000..f26e57b8ec --- /dev/null +++ b/pkg/morph/deploy/group.go @@ -0,0 +1,341 @@ +package deploy + +import ( + "context" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "go.uber.org/zap" +) + +// initCommitteeGroupPrm groups parameters of committee group initialization. +type initCommitteeGroupPrm struct { + logger *zap.Logger + + blockchain Blockchain + + nnsOnChainAddress util.Uint160 + systemEmail string + + committee keys.PublicKeys + localAcc *wallet.Account + localAccCommitteeIndex int + + keyStorage KeyStorage +} + +// initCommitteeGroup initializes committee group and returns corresponding private key. +func initCommitteeGroup(ctx context.Context, prm initCommitteeGroupPrm) (*keys.PrivateKey, error) { + monitor, err := newBlockchainMonitor(prm.logger, prm.blockchain) + if err != nil { + return nil, fmt.Errorf("init blockchain monitor: %w", err) + } + defer monitor.stop() + + inv := invoker.New(prm.blockchain, nil) + const leaderCommitteeIndex = 0 + var committeeGroupKey *keys.PrivateKey + var leaderTick func() + +upperLoop: + for ; ; monitor.waitForNextBlock(ctx) { + select { + case <-ctx.Done(): + return nil, fmt.Errorf("wait for committee group key to be distributed: %w", ctx.Err()) + default: + } + + prm.logger.Info("checking domains with shared committee group key...") + + nShared := 0 + + for i := range prm.committee { + domain := committeeGroupDomainForMember(i) + + rec, err := lookupNNSDomainRecord(inv, prm.nnsOnChainAddress, domain) + if err != nil { + if errors.Is(err, errMissingDomain) || errors.Is(err, errMissingDomainRecord) { + prm.logger.Info("NNS record with committee group key shared with the committee member is still missing, waiting...", + zap.String("domain", domain)) + } else { + prm.logger.Error("failed to lookup NNS domain record, will try again later", + zap.String("domain", domain), zap.Error(err)) + } + + continue + } + + if committeeGroupKey == nil && i == prm.localAccCommitteeIndex { + committeeGroupKey, err = decryptSharedPrivateKey(rec, prm.committee[leaderCommitteeIndex], prm.localAcc.PrivateKey()) + if err != nil { + prm.logger.Error("failed to decrypt shared committee group key, will wait for a background fix", + zap.String("domain", domain), zap.Error(err)) + continue upperLoop + } + } + + nShared++ + } + + if nShared == len(prm.committee) { + prm.logger.Info("committee group key is distributed between all committee members") + return committeeGroupKey, nil + } + + prm.logger.Info("not all committee members received the committee group key, distribution is needed", + zap.Int("need", len(prm.committee)), zap.Int("shared", nShared)) + + if prm.localAccCommitteeIndex != leaderCommitteeIndex { + prm.logger.Info("will wait for distribution from the leader") + continue + } + + if committeeGroupKey == nil { + committeeGroupKey, err = prm.keyStorage.GetPersistedPrivateKey() + if err != nil { + prm.logger.Error("failed to init committee group key, will try again later", zap.Error(err)) + continue + } + } + + if leaderTick == nil { + leaderTick, err = initShareCommitteeGroupKeyAsLeaderTick(prm, monitor, committeeGroupKey) + if err != nil { + prm.logger.Error("failed to construct action sharing committee group key between committee members as leader, will try again later", + zap.Error(err)) + continue + } + } + + leaderTick() + } +} + +// initShareCommitteeGroupKeyAsLeaderTick returns a function that preserves +// context of the committee group key distribution by leading committee member +// between calls. +func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, monitor *blockchainMonitor, committeeGroupKey *keys.PrivateKey) (func(), error) { + _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + if err != nil { + return nil, fmt.Errorf("init transaction sender from local account: %w", err) + } + + _invoker := invoker.New(prm.blockchain, nil) + + // multi-tick context + mDomainsToVubs := make(map[string][2]uint32) // 1st - register, 2nd - addRecord + + return func() { + prm.logger.Info("distributing committee group key between committee members using NNS...") + + for i := range prm.committee { + domain := committeeGroupDomainForMember(i) + l := prm.logger.With(zap.String("domain", domain), zap.Stringer("member", prm.committee[i])) + + l.Info("synchronizing committee group key with NNS domain record...") + + _, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domain) + if err != nil { + if errors.Is(err, errMissingDomain) { + l.Info("NNS domain is missing, registration is needed") + + vubs, ok := mDomainsToVubs[domain] + if ok && vubs[0] > 0 { + l.Info("transaction registering NNS domain was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= vubs[0] { + l.Info("previously sent transaction registering NNS domain may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", vubs[0])) + return + } + + l.Info("previously sent transaction registering NNS domain expired without side-effect") + } + + l.Info("sending new transaction registering domain in the NNS...") + + _, vub, err := _actor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, + domain, _actor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) + if err != nil { + vubs[0] = 0 + mDomainsToVubs[domain] = vubs + if isErrNotEnoughGAS(err) { + l.Info("not enough GAS to register domain in the NNS, will try again later") + } else { + l.Error("failed to send transaction registering domain in the NNS, will try again later", zap.Error(err)) + } + return + } + + vubs[0] = vub + mDomainsToVubs[domain] = vubs + + l.Info("transaction registering domain in the NNS has been successfully sent, will wait for the outcome") + + continue + } else if !errors.Is(err, errMissingDomainRecord) { + l.Error("failed to lookup NNS domain record, will try again later", zap.Error(err)) + return + } + + l.Info("missing record of the NNS domain, needed to be set") + + vubs, ok := mDomainsToVubs[domain] + if ok && vubs[1] > 0 { + l.Info("transaction setting NNS domain record was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= vubs[1] { + l.Info("previously sent transaction setting NNS domain record may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", vubs[1])) + return + } + + l.Info("previously sent transaction setting NNS domain record expired without side-effect") + } + + l.Info("sharing encrypted committee group key with the committee member...") + + keyCipher, err := encryptSharedPrivateKey(committeeGroupKey, prm.localAcc.PrivateKey(), prm.committee[i]) + if err != nil { + l.Error("failed to encrypt committee group key to share with the committee member, will try again later", + zap.Error(err)) + return + } + + l.Info("sending new transaction setting domain record in the NNS...") + + _, vub, err := _actor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, + domain, int64(nns.TXT), keyCipher) + if err != nil { + vubs[1] = 0 + mDomainsToVubs[domain] = vubs + if isErrNotEnoughGAS(err) { + l.Info("not enough GAS to set NNS domain record, will try again later") + } else { + l.Error("failed to send transaction setting NNS domain record, will try again later", zap.Error(err)) + } + return + } + + vubs[1] = vub + mDomainsToVubs[domain] = vubs + + l.Info("transaction setting NNS domain record has been successfully sent, will wait for the outcome") + + continue + } + + l.Info("committee group key is shared with the committee member in NNS domain record") + } + }, nil +} + +// encryptSharedPrivateKey encrypts private key using provided coder's private +// key to be decrypted using decoder's private key. Inverse operation to +// decryptSharedPrivateKey. +func encryptSharedPrivateKey(sharedPrivKey, coderPrivKey *keys.PrivateKey, decoderPubKey *keys.PublicKey) (string, error) { + sharedSecret, err := calculateSharedSecret(coderPrivKey, decoderPubKey) + if err != nil { + return "", fmt.Errorf("calculate shared secret: %w", err) + } + + cipherBlock, err := aes.NewCipher(sharedSecret) + if err != nil { + return "", fmt.Errorf("create AES cipher block: %w", err) + } + + cipherMode, err := cipher.NewGCM(cipherBlock) + if err != nil { + return "", fmt.Errorf("wrap cipher block in GCM: %w", err) + } + + nonce := make([]byte, cipherMode.NonceSize()) + + _, err = rand.Reader.Read(nonce) + if err != nil { + return "", fmt.Errorf("generate nonce using crypto randomizer: %w", err) + } + + bKeyCipher, err := cipherMode.Seal(nonce, nonce, sharedPrivKey.Bytes(), nil), nil + if err != nil { + return "", fmt.Errorf("encrypt key binary: %w", err) + } + + return base64.StdEncoding.EncodeToString(bKeyCipher), nil +} + +// decryptSharedPrivateKey decrypts cipher of the private key encrypted by +// coder's private key. Inverse operation to encryptSharedPrivateKey. +func decryptSharedPrivateKey(sharedPrivKeyCipher string, coderPubKey *keys.PublicKey, decoderPrivKey *keys.PrivateKey) (*keys.PrivateKey, error) { + bKeyCipher, err := base64.StdEncoding.DecodeString(sharedPrivKeyCipher) + if err != nil { + return nil, fmt.Errorf("decode key cipher from base64: %w", err) + } + + sharedSecret, err := calculateSharedSecret(decoderPrivKey, coderPubKey) + if err != nil { + return nil, fmt.Errorf("calculate shared secret: %w", err) + } + + cipherBlock, err := aes.NewCipher(sharedSecret) + if err != nil { + return nil, fmt.Errorf("create AES cipher block: %w", err) + } + + cipherMode, err := cipher.NewGCM(cipherBlock) + if err != nil { + return nil, fmt.Errorf("wrap cipher block in GCM: %w", err) + } + + nonceSize := cipherMode.NonceSize() + if len(sharedPrivKeyCipher) < nonceSize { + return nil, fmt.Errorf("too short cipher %d", len(sharedPrivKeyCipher)) + } + + bSharedPrivKey, err := cipherMode.Open(nil, bKeyCipher[:nonceSize], bKeyCipher[nonceSize:], nil) + if err != nil { + return nil, fmt.Errorf("decrypt cipher: %w", err) + } + + sharedPrivKey, err := keys.NewPrivateKeyFromBytes(bSharedPrivKey) + if err != nil { + return nil, fmt.Errorf("decode key binary: %w", err) + } + + return sharedPrivKey, nil +} + +func calculateSharedSecret(localPrivKey *keys.PrivateKey, remotePubKey *keys.PublicKey) ([]byte, error) { + // this commented code will start working from go1.20 (it's fully compatible + // with current implementation) + // + // localPrivKeyECDH, err := localPrivKey.ECDH() + // if err != nil { + // return nil, fmt.Errorf("local private key to ECDH key: %w", err) + // } + // + // remotePubKeyECDH, err := (*ecdsa.PublicKey)(remotePubKey).ECDH() + // if err != nil { + // return nil, fmt.Errorf("remote public key to ECDH key: %w", err) + // } + // + // sharedSecret, err := localPrivKeyECDH.ECDH(remotePubKeyECDH) + // if err != nil { + // return nil, fmt.Errorf("ECDH exchange: %w", err) + // } + // + // return sharedSecret, nil + + x, _ := localPrivKey.ScalarMult(remotePubKey.X, remotePubKey.Y, localPrivKey.D.Bytes()) + return x.Bytes(), nil +} diff --git a/pkg/morph/deploy/group_test.go b/pkg/morph/deploy/group_test.go new file mode 100644 index 0000000000..9a30d33099 --- /dev/null +++ b/pkg/morph/deploy/group_test.go @@ -0,0 +1,25 @@ +package deploy + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/stretchr/testify/require" +) + +func TestKeySharing(t *testing.T) { + coderKey, err := keys.NewPrivateKey() + require.NoError(t, err) + decoderKey, err := keys.NewPrivateKey() + require.NoError(t, err) + sharedKey, err := keys.NewPrivateKey() + require.NoError(t, err) + + cipher, err := encryptSharedPrivateKey(sharedKey, coderKey, decoderKey.PublicKey()) + require.NoError(t, err) + + restoredKey, err := decryptSharedPrivateKey(cipher, coderKey.PublicKey(), decoderKey) + require.NoError(t, err) + + require.Equal(t, sharedKey, restoredKey) +} diff --git a/pkg/morph/deploy/nns.go b/pkg/morph/deploy/nns.go new file mode 100644 index 0000000000..109a0242ce --- /dev/null +++ b/pkg/morph/deploy/nns.go @@ -0,0 +1,231 @@ +package deploy + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "go.uber.org/zap" +) + +// various NNS domain names. +const ( + domainBootstrap = "bootstrap" + domainDesignateNotaryPrefix = "designate-committee-notary-" + domainDesignateNotaryTx = domainDesignateNotaryPrefix + "tx." + domainBootstrap + domainContractAddresses = "neofs" +) + +func designateNotarySignatureDomainForMember(memberIndex int) string { + return fmt.Sprintf("%s%d.%s", domainDesignateNotaryPrefix, memberIndex, domainBootstrap) +} + +func committeeGroupDomainForMember(memberIndex int) string { + return fmt.Sprintf("committee-group-%d.%s", memberIndex, domainBootstrap) +} + +// various methods of the NeoFS NNS contract. +const ( + methodNNSRegister = "register" + methodNNSResolve = "resolve" + methodNNSAddRecord = "addRecord" + methodNNSSetRecord = "setRecord" +) + +// default NNS domain settings. See DNS specification and also +// https://www.ripe.net/publications/docs/ripe-203. +const ( + nnsRefresh = 3600 + nnsRetry = 600 + nnsExpire = int64(10 * 365 * 24 * time.Hour / time.Second) + nnsMinimum = 3600 +) + +// various NNS errors. +var ( + errMissingDomain = errors.New("missing domain") + errMissingDomainRecord = errors.New("missing domain record") +) + +// deployNNSContractPrm groups parameters of NeoFS NNS contract deployment. +type deployNNSContractPrm struct { + logger *zap.Logger + + blockchain Blockchain + + localAcc *wallet.Account + + localNEF nef.File + localManifest manifest.Manifest + systemEmail string + + // optional constructor of private key for the committee group. If set, it is + // used only when contract is missing. + initCommitteeGroupKey func() (*keys.PrivateKey, error) +} + +// initNNSContract synchronizes NNS contract with the chain and returns the +// address. Success is the presence of NNS contract in the chain with ID=1. +// initNNSContract returns any error encountered due to which the contract +// cannot be synchronized in any way. For example, problems that can be fixed on +// the chain in the background (manually or automatically over time) do not stop +// the procedure. Breaking the context stops execution immediately (so hangup is +// not possible) and the function returns an error. In this case, +// initNNSContract can be re-called (expected after application restart): all +// previously succeeded actions will be skipped, and execution will be continued +// from the last failed stage. +// +// If contract is missing and deployNNSContractPrm.initCommitteeGroupKey is provided, +// initNNSContract attempts to deploy local contract. +func initNNSContract(ctx context.Context, prm deployNNSContractPrm) (res util.Uint160, err error) { + monitor, err := newBlockchainMonitor(prm.logger, prm.blockchain) + if err != nil { + return res, fmt.Errorf("init blockchain monitor: %w", err) + } + defer monitor.stop() + + var managementContract *management.Contract + var sentTxValidUntilBlock uint32 + var committeeGroupKey *keys.PrivateKey + + for ; ; monitor.waitForNextBlock(ctx) { + select { + case <-ctx.Done(): + return res, fmt.Errorf("wait for NNS contract synchronization: %w", ctx.Err()) + default: + } + + prm.logger.Info("reading on-chain state of the NNS contract by ID=1") + + stateOnChain, err := readNNSOnChainState(prm.blockchain) + if err != nil { + prm.logger.Error("failed to read on-chain state of the NNS contract, will try again later", zap.Error(err)) + continue + } + + if stateOnChain != nil { + // declared in https://github.com/nspcc-dev/neofs-contract sources + const nnsContractName = "NameService" + if stateOnChain.Manifest.Name != nnsContractName { + return res, fmt.Errorf("wrong name of the contract with ID=1: expected '%s', got '%s'", + nnsContractName, stateOnChain.Manifest.Name) + } + + return stateOnChain.Hash, nil + } + + if prm.initCommitteeGroupKey == nil { + prm.logger.Info("NNS contract is missing on the chain but attempts to deploy are disabled, will wait for background deployment") + continue + } + + prm.logger.Info("NNS contract is missing on the chain, contract needs to be deployed") + + if committeeGroupKey == nil { + prm.logger.Info("initializing private key for the committee group...") + + committeeGroupKey, err = prm.initCommitteeGroupKey() + if err != nil { + prm.logger.Error("failed to init committee group key, will try again later", zap.Error(err)) + continue + } + + prm.logger.Info("private key of the committee group has been initialized", zap.Stringer("public key", committeeGroupKey.PublicKey())) + } + + if sentTxValidUntilBlock > 0 { + prm.logger.Info("transaction deploying NNS contract was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= sentTxValidUntilBlock { + prm.logger.Info("previously sent transaction deploying NNS contract may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", sentTxValidUntilBlock)) + continue + } + + prm.logger.Info("previously sent transaction deploying NNS contract expired without side-effect") + } + + prm.logger.Info("sending new transaction deploying NNS contract...") + + if managementContract == nil { + _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + if err != nil { + prm.logger.Warn("NNS contract is missing on the chain but attempts to deploy are disabled, will try again later") + continue + } + + managementContract = management.New(_actor) + + setGroupInManifest(&prm.localManifest, prm.localNEF, committeeGroupKey, prm.localAcc.ScriptHash()) + } + + // just to definitely avoid mutation + nefCp := prm.localNEF + manifestCp := prm.localManifest + + _, vub, err := managementContract.Deploy(&nefCp, &manifestCp, []interface{}{ + []interface{}{ + []interface{}{domainBootstrap, prm.systemEmail}, + []interface{}{domainContractAddresses, prm.systemEmail}, + }, + }) + if err != nil { + sentTxValidUntilBlock = 0 + if isErrNotEnoughGAS(err) { + prm.logger.Info("not enough GAS to deploy NNS contract, will try again later") + } else { + prm.logger.Error("failed to send transaction deploying NNS contract, will try again later", zap.Error(err)) + } + continue + } + + sentTxValidUntilBlock = vub + + prm.logger.Info("transaction deploying NNS contract has been successfully sent, will wait for the outcome") + } +} + +// lookupNNSDomainRecord looks up for the 1st record of the NNS domain with +// given name. Returns errMissingDomain if domain doesn't exist. Returns +// errMissingDomainRecord if domain has no records. +func lookupNNSDomainRecord(inv *invoker.Invoker, nnsContract util.Uint160, domainName string) (string, error) { + item, err := unwrap.Item(inv.Call(nnsContract, methodNNSResolve, domainName, int64(nns.TXT))) + if err != nil { + if strings.Contains(err.Error(), "token not found") { + return "", errMissingDomain + } + + return "", fmt.Errorf("call '%s' method of the NNS contract: %w", methodNNSResolve, err) + } + + arr, ok := item.Value().([]stackitem.Item) + if !ok { + if _, ok = item.(stackitem.Null); !ok { + return "", fmt.Errorf("malformed/unsupported response of the NNS '%s' method: expected array, got %s", + methodNNSResolve, item.Type()) + } + } else if len(arr) > 0 { + b, err := arr[0].TryBytes() + if err != nil { + return "", fmt.Errorf("malformed/unsupported 1st array item of the NNS '%s' method response (expected %v): %w", + methodNNSResolve, stackitem.ByteArrayT, err) + } + + return string(b), nil + } + + return "", errMissingDomainRecord +} diff --git a/pkg/morph/deploy/notary.go b/pkg/morph/deploy/notary.go new file mode 100644 index 0000000000..799a705061 --- /dev/null +++ b/pkg/morph/deploy/notary.go @@ -0,0 +1,873 @@ +package deploy + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" + "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/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/wallet" + randutil "github.com/nspcc-dev/neofs-node/pkg/util/rand" + "go.uber.org/zap" +) + +// enableNotaryPrm groups parameters of Notary service initialization parameters +// for the committee. +type enableNotaryPrm struct { + logger *zap.Logger + + blockchain Blockchain + + nnsOnChainAddress util.Uint160 + systemEmail string + + committee keys.PublicKeys + localAcc *wallet.Account + localAccCommitteeIndex int +} + +// enableNotary makes Notary service ready-to-go for the committee members. +func enableNotary(ctx context.Context, prm enableNotaryPrm) error { + monitor, err := newBlockchainMonitor(prm.logger, prm.blockchain) + if err != nil { + return fmt.Errorf("init blockchain monitor: %w", err) + } + defer monitor.stop() + + var tick func() + + if len(prm.committee) == 1 { + prm.logger.Info("committee is single-acc, no multi-signature needed for Notary role designation") + + tick, err = initDesignateNotaryRoleToLocalAccountTick(prm, monitor) + if err != nil { + return fmt.Errorf("construct action designating Notary role to the local account: %w", err) + } + } else { + prm.logger.Info("committee is multi-acc, multi-signature is needed for Notary role designation") + + if prm.localAccCommitteeIndex == 0 { + tick, err = initDesignateNotaryRoleAsLeaderTick(prm, monitor) + if err != nil { + return fmt.Errorf("construct action designating Notary role to the multi-acc committee as leader: %w", err) + } + } else { + tick, err = initDesignateNotaryRoleAsSignerTick(prm, monitor) + if err != nil { + return fmt.Errorf("construct action designating Notary role to the multi-acc committee as signer: %w", err) + } + } + } + + roleContract := rolemgmt.NewReader(invoker.New(prm.blockchain, nil)) + + for ; ; monitor.waitForNextBlock(ctx) { + select { + case <-ctx.Done(): + return fmt.Errorf("wait for Notary service to be enabled for the committee: %w", ctx.Err()) + default: + } + + prm.logger.Info("checking Notary role of the committee members...") + + accsWithNotaryRole, err := roleContract.GetDesignatedByRole(noderoles.P2PNotary, monitor.currentHeight()) + if err != nil { + prm.logger.Error("failed to check role of the committee, will try again later", zap.Error(err)) + continue + } + + someoneWithoutNotaryRole := len(accsWithNotaryRole) < len(prm.committee) + if !someoneWithoutNotaryRole { + for i := range prm.committee { + if !accsWithNotaryRole.Contains(prm.committee[i]) { + someoneWithoutNotaryRole = true + break + } + } + } + if !someoneWithoutNotaryRole { + prm.logger.Info("all committee members have a Notary role") + return nil + } + + prm.logger.Info("not all members of the committee have a Notary role, designation is needed") + + tick() + } +} + +// initDesignateNotaryRoleToLocalAccountTick returns a function that preserves +// context of the Notary role designation to the local account between calls. +func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm, monitor *blockchainMonitor) (func(), error) { + _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + if err != nil { + return nil, fmt.Errorf("init transaction sender from local account: %w", err) + } + + roleContract := rolemgmt.New(_actor) + + // multi-tick context + var sentTxValidUntilBlock uint32 + + return func() { + if sentTxValidUntilBlock > 0 && sentTxValidUntilBlock <= monitor.currentHeight() { + prm.logger.Info("previously sent transaction designating Notary role to the local account may still be relevant, will wait for the outcome") + return + } + + if sentTxValidUntilBlock > 0 { + prm.logger.Info("transaction designating Notary role to the local account was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= sentTxValidUntilBlock { + prm.logger.Info("previously sent transaction designating Notary role to the local account may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", sentTxValidUntilBlock)) + return + } + + prm.logger.Info("previously sent transaction designating Notary role to the local account expired without side-effect") + } + + prm.logger.Info("sending new transaction designating Notary role to the local account...") + + var err error + + _, vub, err := roleContract.DesignateAsRole(noderoles.P2PNotary, keys.PublicKeys{prm.localAcc.PublicKey()}) + if err != nil { + sentTxValidUntilBlock = 0 + if isErrNotEnoughGAS(err) { + prm.logger.Info("not enough GAS to designate Notary role to the local account, will try again later") + } else { + prm.logger.Error("failed to send transaction designating Notary role to the local account, will try again later", zap.Error(err)) + } + return + } + + sentTxValidUntilBlock = vub + + prm.logger.Info("transaction designating Notary role to the local account has been successfully sent, will wait for the outcome") + }, nil +} + +// initDesignateNotaryRoleAsLeaderTick returns a function that preserves context +// of the Notary role designation to the multi-acc committee between calls. The +// operation is performed by the leading committee member which is assigned to +// collect signatures for the corresponding transaction. +func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm, monitor *blockchainMonitor) (func(), error) { + committeeMultiSigM := smartcontract.GetMajorityHonestNodeCount(len(prm.committee)) + committeeMultiSigAcc := wallet.NewAccountFromPrivateKey(prm.localAcc.PrivateKey()) + + err := committeeMultiSigAcc.ConvertMultisig(committeeMultiSigM, prm.committee) + if err != nil { + return nil, fmt.Errorf("compose committee multi-signature account: %w", err) + } + + _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + if err != nil { + return nil, fmt.Errorf("init transaction sender from local account: %w", err) + } + + committeeSigners := []actor.SignerAccount{ + { + Signer: transaction.Signer{ + Account: prm.localAcc.ScriptHash(), + Scopes: transaction.None, + }, + Account: prm.localAcc, + }, + { + Signer: transaction.Signer{ + Account: committeeMultiSigAcc.ScriptHash(), + Scopes: transaction.CalledByEntry, + }, + Account: committeeMultiSigAcc, + }, + } + + committeeActor, err := actor.New(prm.blockchain, committeeSigners) + if err != nil { + return nil, fmt.Errorf("init transaction sender with committee signers: %w", err) + } + + _invoker := invoker.New(prm.blockchain, nil) + roleContract := rolemgmt.New(committeeActor) + + // multi-tick context + var registerDomainTxValidUntilBlock uint32 + var setDomainRecordTxValidUntilBlock uint32 + var tx *transaction.Transaction + var mCommitteeIndexToSignature map[int][]byte + var designateRoleTxValidUntilBlock uint32 + var txFullySigned bool + + resetTx := func() { + tx = nil + setDomainRecordTxValidUntilBlock = 0 + for k := range mCommitteeIndexToSignature { + delete(mCommitteeIndexToSignature, k) + } + designateRoleTxValidUntilBlock = 0 + txFullySigned = false + } + + return func() { + l := prm.logger.With(zap.String("domain", domainDesignateNotaryTx)) + + l.Info("synchronizing shared data of the transaction designating Notary role to the committee with NNS domain record...") + + var sharedTxData sharedTransactionData + + generateAndShareTxData := func(recordExists bool) { + resetTx() + + prm.logger.Info("generating shared data for the transaction designating Notary role to the committee...") + + ver, err := prm.blockchain.GetVersion() + if err != nil { + prm.logger.Error("failed request Neo protocol configuration, will try again later", zap.Error(err)) + return + } + + // _actor.CalculateValidUntilBlock is not used because it is rather "idealized" + // in terms of the accessibility of committee member nodes. So, we need a more + // practically viable timeout to reduce the chance of transaction re-creation. + const defaultValidUntilBlockIncrement = 120 // ~30m for 15s block interval + var txValidUntilBlock uint32 + + if defaultValidUntilBlockIncrement <= ver.Protocol.MaxValidUntilBlockIncrement { + txValidUntilBlock = monitor.currentHeight() + defaultValidUntilBlockIncrement + } else { + txValidUntilBlock = monitor.currentHeight() + ver.Protocol.MaxValidUntilBlockIncrement + } + + strSharedTxData := sharedTransactionData{ + sender: _actor.Sender(), + validUntilBlock: txValidUntilBlock, + nonce: randutil.Uint32(), + }.encodeToString() + + l.Info("sending new transaction setting domain record in the NNS...") + + var vub uint32 + if recordExists { + _, vub, err = _actor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, + domainDesignateNotaryTx, int64(nns.TXT), 0, strSharedTxData) + } else { + _, vub, err = _actor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, + domainDesignateNotaryTx, int64(nns.TXT), strSharedTxData) + } + if err != nil { + setDomainRecordTxValidUntilBlock = 0 + if isErrNotEnoughGAS(err) { + prm.logger.Info("not enough GAS to set NNS domain record, will try again later") + } else { + prm.logger.Error("failed to send transaction setting NNS domain record, will try again later", zap.Error(err)) + } + return + } + + setDomainRecordTxValidUntilBlock = vub + + l.Info("transaction setting NNS domain record has been successfully sent, will wait for the outcome") + } + + strSharedTxData, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domainDesignateNotaryTx) + if err != nil { + if errors.Is(err, errMissingDomain) { + l.Info("NNS domain is missing, registration is needed") + + if registerDomainTxValidUntilBlock > 0 { + l.Info("transaction registering NNS domain was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= registerDomainTxValidUntilBlock { + l.Info("previously sent transaction registering NNS domain may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", registerDomainTxValidUntilBlock)) + return + } + + l.Info("previously sent transaction registering NNS domain expired without side-effect") + } + + l.Info("sending new transaction registering domain in the NNS...") + + _, vub, err := _actor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, + domainDesignateNotaryTx, _actor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) + if err != nil { + registerDomainTxValidUntilBlock = 0 + if isErrNotEnoughGAS(err) { + prm.logger.Info("not enough GAS to register domain in the NNS, will try again later") + } else { + prm.logger.Error("failed to send transaction registering domain in the NNS, will try again later", zap.Error(err)) + } + return + } + + registerDomainTxValidUntilBlock = vub + + l.Info("transaction registering domain in the NNS has been successfully sent, will wait for the outcome") + + return + } else if !errors.Is(err, errMissingDomainRecord) { + l.Error("failed to lookup NNS domain record, will try again later", zap.Error(err)) + return + } + + l.Info("missing record of the NNS domain, needed to be set") + + if setDomainRecordTxValidUntilBlock > 0 { + l.Info("transaction setting NNS domain record was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= setDomainRecordTxValidUntilBlock { + l.Info("previously sent transaction setting NNS domain record may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", setDomainRecordTxValidUntilBlock)) + return + } + + l.Info("previously sent transaction setting NNS domain record expired without side-effect") + } + + generateAndShareTxData(false) + return + } + + err = sharedTxData.decodeString(strSharedTxData) + if err != nil { + l.Error("failed to decode shared data of the transaction got from the NNS domain record, will wait for a background fix", + zap.Error(err)) + return + } + + if cur := monitor.currentHeight(); cur > sharedTxData.validUntilBlock { + l.Error("previously used shared data of the transaction expired, need a reset", + zap.Uint32("expires after height", sharedTxData.validUntilBlock), zap.Uint32("current height", cur)) + generateAndShareTxData(true) + return + } + + l.Info("shared data of the transaction designating Notary role to the committee synchronized successfully", + zap.Uint32("nonce", sharedTxData.nonce), zap.Uint32("expires after height", sharedTxData.validUntilBlock), + zap.Stringer("sender", sharedTxData.sender), + ) + + if tx == nil || !sharedTxDataMatches(tx, sharedTxData) { + prm.logger.Info("making new transaction designating Notary role to the committee...") + + tx, err = makeUnsignedDesignateCommitteeNotaryTx(roleContract, prm.committee, sharedTxData) + if err != nil { + prm.logger.Error("failed to make unsigned transaction designating Notary role to the committee, will try again later", + zap.Error(err)) + return + } + + prm.logger.Info("transaction designating Notary role to the committee initialized, signing...") + + netMagic := _actor.GetNetwork() + + err = prm.localAcc.SignTx(netMagic, tx) + if err != nil { + prm.logger.Error("failed to sign transaction designating Notary role to the committee by local node's account, will try again later", + zap.Error(err)) + return + } + + err = committeeMultiSigAcc.SignTx(netMagic, tx) + if err != nil { + prm.logger.Error("failed to sign transaction designating Notary role to the committee by committee multi-signature account, will try again later", + zap.Error(err)) + return + } + + prm.logger.Info("new transaction designating Notary role to the committee successfully made") + } else { + prm.logger.Info("previously made transaction designating Notary role to the committee is still relevant, continue with it") + } + + needRemoteSignatures := committeeMultiSigM - 1 // -1 local, we always have it + + if len(mCommitteeIndexToSignature) < needRemoteSignatures { + if mCommitteeIndexToSignature == nil { + mCommitteeIndexToSignature = make(map[int][]byte, needRemoteSignatures) + } + + prm.logger.Info("collecting signatures of the transaction designating notary role to the committee from other members using NNS...") + + var invalidSignatureCounter int + + for i := range prm.committee[1:] { + domain := designateNotarySignatureDomainForMember(i) + + rec, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domain) + if err != nil { + if errors.Is(err, errMissingDomain) || errors.Is(err, errMissingDomainRecord) { + prm.logger.Info("missing NNS domain record with committee member's signature of the transaction designating Notary role to the committee, will wait", + zap.Stringer("member", prm.committee[i]), + zap.String("domain", domain)) + } else { + prm.logger.Error("failed to read NNS domain record with committee member's signature of the transaction designating Notary role to the committee, will try again later", + zap.Stringer("member", prm.committee[i]), + zap.String("domain", domain), + zap.Error(err)) + } + continue + } + + bRec, err := base64.StdEncoding.DecodeString(rec) + if err != nil { + prm.logger.Info("failed to decode NNS domain record with committee member's signature of the transaction designating Notary role to the committee from base64, will wait for a background fix", + zap.Stringer("member", prm.committee[i]), + zap.String("domain", domain), + zap.Error(err)) + continue + } + + checksumMatches, bSignature := sharedTxData.shiftChecksum(bRec) + if !checksumMatches { + prm.logger.Info("checksum of shared data of the transaction designating Notary role to the committee submitted by committee member mismatches, skip signature", + zap.Stringer("member", prm.committee[i]), + zap.String("domain", domain)) + continue + } + + txCp := *tx // to safely call Hash method below + if !prm.committee[i].VerifyHashable(bSignature, uint32(_actor.GetNetwork()), &txCp) { + prm.logger.Info("invalid signature of the transaction designating Notary role to the committee submitted by committee member", + zap.Stringer("member", prm.committee[i]), + zap.String("domain", domain)) + + invalidSignatureCounter++ + + if invalidSignatureCounter+committeeMultiSigM > len(prm.committee) { + prm.logger.Info("number of invalid signatures of the transaction designating Notary role to the committee submitted by remote members exceeded the threshold, will recreate the transaction", + zap.Int("invalid", invalidSignatureCounter), zap.Int("need", committeeMultiSigM), + zap.Int("total members", len(prm.committee))) + generateAndShareTxData(true) + return + } + + continue + } + + prm.logger.Info("received valid signature of the transaction designating Notary role to the committee submitted by committee member", + zap.Stringer("member", prm.committee[i]), + zap.String("domain", domain)) + + mCommitteeIndexToSignature[i] = bSignature + if len(mCommitteeIndexToSignature) == needRemoteSignatures { + break + } + } + + if len(mCommitteeIndexToSignature) < needRemoteSignatures { + prm.logger.Info("there are still not enough signatures of the transaction designating Notary role to the committee in the NNS, will wait", + zap.Int("need", needRemoteSignatures), zap.Int("got", len(mCommitteeIndexToSignature))) + return + } + } + + prm.logger.Info("gathered enough signatures of the transaction designating Notary role to the committee") + + if designateRoleTxValidUntilBlock > 0 { + prm.logger.Info("transaction designating Notary role to the committee was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= designateRoleTxValidUntilBlock { + prm.logger.Info("previously sent transaction designating Notary role to the committee may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", designateRoleTxValidUntilBlock)) + return + } + + prm.logger.Info("previously sent transaction designating Notary role to the committee expired without side-effect, will recreate") + generateAndShareTxData(true) + return + } + + if !txFullySigned { + prm.logger.Info("finalizing the transaction designating Notary role to the committee...") + + initialLen := len(tx.Scripts[1].InvocationScript) + var extraLen int + + for _, sig := range mCommitteeIndexToSignature { + extraLen += 1 + 1 + len(sig) // opcode + length + value + } + + tx.Scripts[1].InvocationScript = append(tx.Scripts[1].InvocationScript, + make([]byte, extraLen)...) + buf := tx.Scripts[1].InvocationScript[initialLen:] + + for _, sig := range mCommitteeIndexToSignature { + buf[0] = byte(opcode.PUSHDATA1) + buf[1] = byte(len(sig)) + buf = buf[2:] + buf = buf[copy(buf, sig):] + } + + txFullySigned = true + } + + prm.logger.Info("sending the transaction designating Notary role to the committee...") + + _, vub, err := _actor.Send(tx) + if err != nil { + designateRoleTxValidUntilBlock = 0 + switch { + default: + prm.logger.Error("failed to send transaction designating Notary role to the committee, will try again later", + zap.Error(err)) + case isErrNotEnoughGAS(err): + prm.logger.Info("not enough GAS for transaction designating Notary role to the committee, will try again later") + case isErrInvalidTransaction(err): + prm.logger.Warn("composed transaction designating Notary role to the committee is invalid and can't be sent, will recreate", + zap.Error(err)) + generateAndShareTxData(true) + } + return + } + + designateRoleTxValidUntilBlock = vub + + prm.logger.Info("transaction designating Notary role to the committee has been successfully sent, will wait for the outcome") + }, nil +} + +// initDesignateNotaryRoleAsSignerTick returns a function that preserves context +// of the Notary role designation to the multi-acc committee between calls. The +// operation is performed by the non-leading committee member which is assigned to +// sign transaction submitted by the leader. +func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm, monitor *blockchainMonitor) (func(), error) { + committeeMultiSigM := smartcontract.GetMajorityHonestNodeCount(len(prm.committee)) + committeeMultiSigAcc := wallet.NewAccountFromPrivateKey(prm.localAcc.PrivateKey()) + + err := committeeMultiSigAcc.ConvertMultisig(committeeMultiSigM, prm.committee) + if err != nil { + return nil, fmt.Errorf("compose committee multi-signature account: %w", err) + } + + _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + if err != nil { + return nil, fmt.Errorf("init transaction sender from local account: %w", err) + } + + committeeSigners := []actor.SignerAccount{ + { + Signer: transaction.Signer{ + Account: prm.localAcc.ScriptHash(), + Scopes: transaction.None, + }, + Account: prm.localAcc, + }, + { + Signer: transaction.Signer{ + Account: committeeMultiSigAcc.ScriptHash(), + Scopes: transaction.CalledByEntry, + }, + Account: committeeMultiSigAcc, + }, + } + + committeeActor, err := actor.New(prm.blockchain, committeeSigners) + if err != nil { + return nil, fmt.Errorf("init transaction sender with committee signers: %w", err) + } + + _invoker := invoker.New(prm.blockchain, nil) + roleContract := rolemgmt.New(committeeActor) + + // multi-tick context + var tx *transaction.Transaction + var registerDomainTxValidUntilBlock uint32 + var setDomainRecordTxValidUntilBlock uint32 + + resetTx := func() { + tx = nil + setDomainRecordTxValidUntilBlock = 0 + } + + return func() { + l := prm.logger.With(zap.String("domain", domainDesignateNotaryTx)) + + prm.logger.Info("synchronizing shared data of the transaction designating Notary role to the committee with NNS domain record...") + + strSharedTxData, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domainDesignateNotaryTx) + if err != nil { + switch { + default: + l.Error("failed to lookup NNS domain record, will try again later", zap.Error(err)) + case errors.Is(err, errMissingDomain): + l.Info("NNS domain is missing, will wait for a leader") + case errors.Is(err, errMissingDomainRecord): + l.Info("missing record in the NNS domain, will wait for a leader") + } + return + } + + var sharedTxData sharedTransactionData + + err = sharedTxData.decodeString(strSharedTxData) + if err != nil { + l.Error("failed to decode shared data of the transaction got from the NNS domain record, will wait for a background fix", + zap.Error(err)) + return + } + + if cur := monitor.currentHeight(); cur > sharedTxData.validUntilBlock { + l.Error("previously used shared data of the transaction expired, will wait for update by leader", + zap.Uint32("expires after height", sharedTxData.validUntilBlock), zap.Uint32("current height", cur)) + resetTx() + return + } + + l.Info("shared data of the transaction designating Notary role to the committee synchronized successfully", + zap.Uint32("nonce", sharedTxData.nonce), zap.Uint32("expires after height", sharedTxData.validUntilBlock), + zap.Stringer("sender", sharedTxData.sender), + ) + + if tx == nil || !sharedTxDataMatches(tx, sharedTxData) { + prm.logger.Info("recreating the transaction designating Notary role to the committee...") + + tx, err = makeUnsignedDesignateCommitteeNotaryTx(roleContract, prm.committee, sharedTxData) + if err != nil { + prm.logger.Error("failed to make unsigned transaction designating Notary role to the committee, will try again later", + zap.Error(err)) + return + } + + prm.logger.Info("transaction designating Notary role to the committee successfully recreated") + } else { + prm.logger.Info("previously made transaction designating Notary role to the committee is still relevant, continue with it") + } + + domain := designateNotarySignatureDomainForMember(prm.localAccCommitteeIndex) + + l = prm.logger.With(zap.String("domain", domain)) + + var recordExists bool + var needReset bool + + rec, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domain) + if err != nil { + if errors.Is(err, errMissingDomain) { + l.Info("NNS domain is missing, registration is needed") + + if registerDomainTxValidUntilBlock > 0 { + l.Info("transaction registering NNS domain was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= registerDomainTxValidUntilBlock { + l.Info("previously sent transaction registering NNS domain may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", registerDomainTxValidUntilBlock)) + return + } + + l.Info("previously sent transaction registering NNS domain expired without side-effect") + } + + l.Info("sending new transaction registering domain in the NNS...") + + _, vub, err := _actor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, + domain, _actor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) + if err != nil { + registerDomainTxValidUntilBlock = 0 + if isErrNotEnoughGAS(err) { + prm.logger.Info("not enough GAS to register domain in the NNS, will try again later") + } else { + prm.logger.Error("failed to send transaction registering domain in the NNS, will try again later", zap.Error(err)) + } + return + } + + registerDomainTxValidUntilBlock = vub + + l.Info("transaction registering domain in the NNS has been successfully sent, will wait for the outcome") + + return + } else if !errors.Is(err, errMissingDomainRecord) { + l.Error("failed to lookup NNS domain record, will try again later", zap.Error(err)) + return + } + + l.Info("missing record of the NNS domain, needed to be set") + + if setDomainRecordTxValidUntilBlock > 0 { + l.Info("transaction setting NNS domain record was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= setDomainRecordTxValidUntilBlock { + l.Info("previously sent transaction setting NNS domain record may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", setDomainRecordTxValidUntilBlock)) + return + } + + l.Info("previously sent transaction setting NNS domain record expired without side-effect") + } + + needReset = true + } else { + bRec, err := base64.StdEncoding.DecodeString(rec) + if err != nil { + l.Info("failed to decode NNS domain record with local account's signature of the transaction designating Notary role to the committee from base64, will wait for a background fix", + zap.String("domain", domain), zap.Error(err)) + return + } + + checksumMatches, bSignature := sharedTxData.shiftChecksum(bRec) + if !checksumMatches { + l.Info("checksum of shared data of the transaction designating Notary role to the committee submitted by committee member mismatches, need to be recalculated") + needReset = true + } else { + txCp := *tx // to safely call Hash method below + if !prm.localAcc.PublicKey().VerifyHashable(bSignature, uint32(_actor.GetNetwork()), &txCp) { + l.Info("invalid signature of the transaction designating Notary role to the committee submitted by local account, need to be recalculated") + needReset = true + } + } + + recordExists = true + } + + if needReset { + prm.logger.Info("calculating signature of the transaction designating Notary role to the committee using local account...") + + sig := prm.localAcc.SignHashable(_actor.GetNetwork(), tx) + sig = sharedTxData.unshiftChecksum(sig) + + rec = base64.StdEncoding.EncodeToString(sig) + + l.Info("sending new transaction setting domain record in the NNS...") + + var vub uint32 + if recordExists { + _, vub, err = _actor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, + domain, int64(nns.TXT), 0, rec) + } else { + _, vub, err = _actor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, + domain, int64(nns.TXT), rec) + } + if err != nil { + setDomainRecordTxValidUntilBlock = 0 + if isErrNotEnoughGAS(err) { + prm.logger.Info("not enough GAS to set NNS domain record, will try again later") + } else { + prm.logger.Error("failed to send transaction setting NNS domain record, will try again later", zap.Error(err)) + } + return + } + + setDomainRecordTxValidUntilBlock = vub + + l.Info("transaction setting NNS domain record has been successfully sent, will wait for the outcome") + + return + } + }, nil +} + +// sharedTransactionData groups transaction parameters that cannot be predicted +// in a decentralized way and need to be sent out. +type sharedTransactionData struct { + sender util.Uint160 + validUntilBlock uint32 + nonce uint32 +} + +// bytes serializes sharedTransactionData. +func (x sharedTransactionData) bytes() []byte { + b := make([]byte, sharedTransactionDataLen) + // fixed size is more convenient for potential format changes in the future + copy(b, x.sender.BytesBE()) + binary.BigEndian.PutUint32(b[util.Uint160Size:], x.validUntilBlock) + binary.BigEndian.PutUint32(b[util.Uint160Size+4:], x.nonce) + return b +} + +// encodeToString returns serialized sharedTransactionData in base64. +func (x sharedTransactionData) encodeToString() string { + return base64.StdEncoding.EncodeToString(x.bytes()) +} + +// decodeString decodes serialized sharedTransactionData from base64. +func (x *sharedTransactionData) decodeString(s string) (err error) { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return fmt.Errorf("decode shared transaction data from base64: %w", err) + } + + if len(b) != sharedTransactionDataLen { + return fmt.Errorf("invalid/unsupported length of shared transaction data: expected %d, got %d", + sharedTransactionDataLen, len(b)) + } + + x.sender, err = util.Uint160DecodeBytesBE(b[:util.Uint160Size]) + if err != nil { + return fmt.Errorf("decode sender account binary: %w", err) + } + + x.validUntilBlock = binary.BigEndian.Uint32(b[util.Uint160Size:]) + x.nonce = binary.BigEndian.Uint32(b[util.Uint160Size+4:]) + + return nil +} + +const ( + sharedTransactionDataLen = util.Uint160Size + 4 + 4 + sharedTransactionDataChecksumLen = 4 +) + +// unshiftChecksum prepends given payload with first 4 bytes of the +// sharedTransactionData SHA-256 checksum. Inverse operation to shiftChecksum. +func (x sharedTransactionData) unshiftChecksum(data []byte) []byte { + h := sha256.Sum256(x.bytes()) + return append(h[:sharedTransactionDataChecksumLen], data...) +} + +// shiftChecksum matches checksum of the sharedTransactionData and returns +// payload. Inverse operation to unshiftChecksum. +func (x sharedTransactionData) shiftChecksum(data []byte) (bool, []byte) { + if len(data) < sharedTransactionDataChecksumLen { + return false, data + } + + h := sha256.Sum256(x.bytes()) + if !bytes.HasPrefix(data, h[:sharedTransactionDataChecksumLen]) { + return false, nil + } + + return true, data[sharedTransactionDataChecksumLen:] +} + +// sharedTxDataMatches checks if given transaction is constructed using provided +// shared parameters. +func sharedTxDataMatches(tx *transaction.Transaction, sharedTxData sharedTransactionData) bool { + return sharedTxData.nonce == tx.Nonce && + sharedTxData.validUntilBlock == tx.ValidUntilBlock && + len(tx.Signers) > 0 && tx.Signers[0].Account.Equals(sharedTxData.sender) +} + +// makeUnsignedDesignateCommitteeNotaryTx constructs unsigned transaction that +// designates Notary role to the specified committee members using shared +// parameters. +// +// Note: RoleManagement contract client must be initialized with two signers: +// 1. simple account with transaction.None witness scope +// 2. committee multi-signature account with transaction.CalledByEntry witness scope +func makeUnsignedDesignateCommitteeNotaryTx(roleContract *rolemgmt.Contract, committee keys.PublicKeys, sharedTxData sharedTransactionData) (*transaction.Transaction, error) { + tx, err := roleContract.DesignateAsRoleUnsigned(noderoles.P2PNotary, committee) + if err != nil { + return nil, err + } + + tx.ValidUntilBlock = sharedTxData.validUntilBlock + tx.Nonce = sharedTxData.nonce + tx.Signers[0].Account = sharedTxData.sender + + return tx, nil +} diff --git a/pkg/morph/deploy/util.go b/pkg/morph/deploy/util.go new file mode 100644 index 0000000000..e2e7d18eda --- /dev/null +++ b/pkg/morph/deploy/util.go @@ -0,0 +1,168 @@ +package deploy + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/util" + "go.uber.org/atomic" + "go.uber.org/zap" +) + +// Error functions won't be needed when proper Neo status codes will arrive +// Track https://github.com/nspcc-dev/neofs-node/issues/2285 + +func isErrContractNotFound(err error) bool { + return strings.Contains(err.Error(), "Unknown contract") +} + +func isErrNotEnoughGAS(err error) bool { + return errors.Is(err, neorpc.ErrValidationFailed) && strings.Contains(err.Error(), "insufficient funds") +} + +func isErrInvalidTransaction(err error) bool { + return errors.Is(err, neorpc.ErrValidationFailed) +} + +func setGroupInManifest(_manifest *manifest.Manifest, _nef nef.File, groupPrivKey *keys.PrivateKey, deployerAcc util.Uint160) { + contractAddress := state.CreateContractHash(deployerAcc, _nef.Checksum, _manifest.Name) + sig := groupPrivKey.Sign(contractAddress.BytesBE()) + groupPubKey := groupPrivKey.PublicKey() + + ind := -1 + + for i := range _manifest.Groups { + if _manifest.Groups[i].PublicKey.Equal(groupPubKey) { + ind = i + break + } + } + + if ind >= 0 { + _manifest.Groups[ind].Signature = sig + return + } + + _manifest.Groups = append(_manifest.Groups, manifest.Group{ + PublicKey: groupPubKey, + Signature: sig, + }) +} + +// blockchainMonitor is a thin utility around Blockchain providing state +// monitoring. +type blockchainMonitor struct { + logger *zap.Logger + + blockchain Blockchain + + blockInterval time.Duration + + subID string + height atomic.Uint32 +} + +// newBlockchainMonitor constructs and runs monitor for the given Blockchain. +// Resulting blockchainMonitor must be stopped when no longer needed. +func newBlockchainMonitor(l *zap.Logger, b Blockchain) (*blockchainMonitor, error) { + ver, err := b.GetVersion() + if err != nil { + return nil, fmt.Errorf("request Neo protocol configuration: %w", err) + } + + initialBlock, err := b.GetBlockCount() + if err != nil { + return nil, fmt.Errorf("get current blockchain height: %w", err) + } + + blockCh := make(chan *block.Block) + + newBlockSubID, err := b.ReceiveBlocks(nil, blockCh) + if err != nil { + return nil, fmt.Errorf("subscribe to new blocks of the chain: %w", err) + } + + res := &blockchainMonitor{ + logger: l, + blockchain: b, + blockInterval: time.Duration(ver.Protocol.MillisecondsPerBlock) * time.Millisecond, + subID: newBlockSubID, + } + + res.height.Store(initialBlock) + + go func() { + l.Info("listening to new blocks...") + for { + b, ok := <-blockCh + if !ok { + l.Info("listening to new blocks stopped") + return + } + + res.height.Store(b.Index) + + l.Info("new block arrived", zap.Uint32("height", b.Index)) + } + }() + + return res, nil +} + +// currentHeight returns current blockchain height. +func (x *blockchainMonitor) currentHeight() uint32 { + return x.height.Load() +} + +// waitForNextBlock blocks until blockchainMonitor encounters new block on the +// chain or provided context is done. +func (x *blockchainMonitor) waitForNextBlock(ctx context.Context) { + initialBlock := x.currentHeight() + + ticker := time.NewTicker(x.blockInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if x.height.Load() > initialBlock { + return + } + } + } +} + +// stop stops running blockchainMonitor. Stopped blockchainMonitor must not be +// used anymore. +func (x *blockchainMonitor) stop() { + err := x.blockchain.Unsubscribe(x.subID) + if err != nil { + x.logger.Warn("failed to cancel subscription to new blocks", zap.Error(err)) + } +} + +// readNNSOnChainState reads state of the NeoFS NNS contract in the given +// Blockchain. Returns both nil if contract is missing. +func readNNSOnChainState(b Blockchain) (*state.Contract, error) { + // NNS must always have ID=1 in the NeoFS Sidechain + const nnsContractID = 1 + res, err := b.GetContractStateByID(nnsContractID) + if err != nil { + if isErrContractNotFound(err) { + return nil, nil + } + return nil, fmt.Errorf("read contract state by ID=%d: %w", nnsContractID, err) + } + return res, nil +} diff --git a/pkg/morph/event/subnet/delete.go b/pkg/morph/event/subnet/delete.go deleted file mode 100644 index 2c3ae9d919..0000000000 --- a/pkg/morph/event/subnet/delete.go +++ /dev/null @@ -1,63 +0,0 @@ -package subnetevents - -import ( - "fmt" - - "github.com/nspcc-dev/neo-go/pkg/core/state" - "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" -) - -// Delete structures information about the notification generated by Delete method of Subnet contract. -type Delete struct { - txHash util.Uint256 - - id []byte -} - -// MorphEvent implements Neo:Morph Event interface. -func (Delete) MorphEvent() {} - -// ID returns identifier of the removed subnet in a binary format of NeoFS API protocol. -func (x Delete) ID() []byte { - return x.id -} - -// TxHash returns hash of the transaction which thrown the notification event. -// Makes sense only in notary environments. -func (x Delete) TxHash() util.Uint256 { - return x.txHash -} - -// ParseDelete parses the notification about the removal of a subnet which has been thrown -// by the appropriate method of the Subnet contract. -// -// Resulting event is of Delete type. -func ParseDelete(e *state.ContainedNotificationEvent) (event.Event, error) { - var ( - ev Delete - err error - ) - - items, err := event.ParseStackArray(e) - if err != nil { - return nil, fmt.Errorf("parse stack array: %w", err) - } - - const itemNumDelete = 1 - - if ln := len(items); ln != itemNumDelete { - return nil, event.WrongNumberOfParameters(itemNumDelete, ln) - } - - // parse ID - ev.id, err = client.BytesFromStackItem(items[0]) - if err != nil { - return nil, fmt.Errorf("id item: %w", err) - } - - ev.txHash = e.Container - - return ev, nil -} diff --git a/pkg/morph/event/subnet/delete_test.go b/pkg/morph/event/subnet/delete_test.go deleted file mode 100644 index 28c9c84713..0000000000 --- a/pkg/morph/event/subnet/delete_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package subnetevents_test - -import ( - "testing" - - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - subnetevents "github.com/nspcc-dev/neofs-node/pkg/morph/event/subnet" - "github.com/stretchr/testify/require" -) - -func TestParseDelete(t *testing.T) { - id := []byte("id") - - t.Run("wrong number of items", func(t *testing.T) { - prms := []stackitem.Item{ - stackitem.NewByteArray(nil), - stackitem.NewByteArray(nil), - } - - _, err := subnetevents.ParseDelete(createNotifyEventFromItems(prms)) - require.Error(t, err) - }) - - t.Run("wrong id item", func(t *testing.T) { - _, err := subnetevents.ParseDelete(createNotifyEventFromItems([]stackitem.Item{ - stackitem.NewMap(), - })) - - require.Error(t, err) - }) - - t.Run("correct behavior", func(t *testing.T) { - ev, err := subnetevents.ParseDelete(createNotifyEventFromItems([]stackitem.Item{ - stackitem.NewByteArray(id), - })) - require.NoError(t, err) - - v := ev.(subnetevents.Delete) - - require.Equal(t, id, v.ID()) - }) -} diff --git a/pkg/morph/event/subnet/put.go b/pkg/morph/event/subnet/put.go deleted file mode 100644 index a5fd5c2132..0000000000 --- a/pkg/morph/event/subnet/put.go +++ /dev/null @@ -1,99 +0,0 @@ -package subnetevents - -import ( - "fmt" - - "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/network/payload" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neofs-node/pkg/morph/event" -) - -// Put structures information about the notification generated by Put method of Subnet contract. -type Put struct { - notaryRequest *payload.P2PNotaryRequest - - txHash util.Uint256 - - id []byte - - owner []byte - - info []byte -} - -// MorphEvent implements Neo:Morph Event interface. -func (Put) MorphEvent() {} - -// ID returns identifier of the creating subnet in a binary format of NeoFS API protocol. -func (x Put) ID() []byte { - return x.id -} - -// Owner returns subnet owner's public key in a binary format. -func (x Put) Owner() []byte { - return x.owner -} - -// Info returns information about the subnet in a binary format of NeoFS API protocol. -func (x Put) Info() []byte { - return x.info -} - -// TxHash returns hash of the transaction which thrown the notification event. -// Makes sense only in notary environments. -func (x Put) TxHash() util.Uint256 { - return x.txHash -} - -// NotaryMainTx returns main transaction of the request in the Notary service. -// Returns nil in non-notary environments. -func (x Put) NotaryMainTx() *transaction.Transaction { - return x.notaryRequest.MainTransaction -} - -// number of items in notification about subnet creation. -const itemNumPut = 3 - -// ParseNotaryPut parses the notary notification about the creation of a subnet which has been -// thrown by the appropriate method of the subnet contract. -// -// Resulting event is of Put type. -func ParseNotaryPut(e event.NotaryEvent) (event.Event, error) { - var put Put - - put.notaryRequest = e.Raw() - if put.notaryRequest == nil { - panic(fmt.Sprintf("nil %T in notary environment", put.notaryRequest)) - } - - var ( - err error - - prms = e.Params() - ) - - if ln := len(prms); ln != itemNumPut { - return nil, event.WrongNumberOfParameters(itemNumPut, ln) - } - - // parse info about subnet - put.info, err = event.BytesFromOpcode(prms[0]) - if err != nil { - return nil, fmt.Errorf("info param: %w", err) - } - - // parse owner - put.owner, err = event.BytesFromOpcode(prms[1]) - if err != nil { - return nil, fmt.Errorf("creator param: %w", err) - } - - // parse ID - put.id, err = event.BytesFromOpcode(prms[2]) - if err != nil { - return nil, fmt.Errorf("id param: %w", err) - } - - return put, nil -} diff --git a/pkg/morph/event/subnet/remove_node.go b/pkg/morph/event/subnet/remove_node.go deleted file mode 100644 index a8793dc292..0000000000 --- a/pkg/morph/event/subnet/remove_node.go +++ /dev/null @@ -1,69 +0,0 @@ -package subnetevents - -import ( - "fmt" - - "github.com/nspcc-dev/neo-go/pkg/core/state" - "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" -) - -// RemoveNode structure of subnet.RemoveNode notification from morph chain. -type RemoveNode struct { - subnetID []byte - nodeKey []byte - - // txHash is used in notary environmental - // for calculating unique but same for - // all notification receivers values. - txHash util.Uint256 -} - -// MorphEvent implements Neo:Morph Event interface. -func (RemoveNode) MorphEvent() {} - -// SubnetworkID returns a marshalled subnetID structure, defined in API. -func (rn RemoveNode) SubnetworkID() []byte { return rn.subnetID } - -// Node is public key of the nodeKey that is being deleted. -func (rn RemoveNode) Node() []byte { return rn.nodeKey } - -// TxHash returns hash of the TX with RemoveNode -// notification. -func (rn RemoveNode) TxHash() util.Uint256 { return rn.txHash } - -const expectedItemNumRemoveNode = 2 - -// ParseRemoveNode parses notification into subnet event structure. -// -// Expects 2 stack items. -func ParseRemoveNode(e *state.ContainedNotificationEvent) (event.Event, error) { - var ( - ev RemoveNode - err error - ) - - params, err := event.ParseStackArray(e) - if err != nil { - return nil, fmt.Errorf("could not parse stack items from notify event: %w", err) - } - - if ln := len(params); ln != expectedItemNumRemoveNode { - return nil, event.WrongNumberOfParameters(expectedItemNumRemoveNode, ln) - } - - ev.subnetID, err = client.BytesFromStackItem(params[0]) - if err != nil { - return nil, fmt.Errorf("could not get raw subnetID: %w", err) - } - - ev.nodeKey, err = client.BytesFromStackItem(params[1]) - if err != nil { - return nil, fmt.Errorf("could not get raw public key of the node: %w", err) - } - - ev.txHash = e.Container - - return ev, nil -} diff --git a/pkg/morph/event/subnet/remove_node_test.go b/pkg/morph/event/subnet/remove_node_test.go deleted file mode 100644 index 57d00326e6..0000000000 --- a/pkg/morph/event/subnet/remove_node_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package subnetevents_test - -import ( - "testing" - - "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - . "github.com/nspcc-dev/neofs-node/pkg/morph/event/subnet" - subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" - "github.com/stretchr/testify/require" -) - -func TestParseRemoveNode(t *testing.T) { - t.Run("wrong number of arguments", func(t *testing.T) { - _, err := ParseRemoveNode(createNotifyEventFromItems([]stackitem.Item{})) - require.Error(t, err) - }) - - t.Run("invalid item type", func(t *testing.T) { - args := []stackitem.Item{stackitem.NewMap(), stackitem.Make(123)} - _, err := ParseRemoveNode(createNotifyEventFromItems(args)) - require.Error(t, err) - }) - - subnetID := subnetid.ID{} - subnetID.SetNumeric(123) - - rawSubnetID := subnetID.Marshal() - - priv, err := keys.NewPrivateKey() - require.NoError(t, err) - - pub := priv.PublicKey() - - t.Run("good", func(t *testing.T) { - args := []stackitem.Item{stackitem.NewByteArray(rawSubnetID), stackitem.Make(pub.Bytes())} - - e, err := ParseRemoveNode(createNotifyEventFromItems(args)) - require.NoError(t, err) - - gotRaw := e.(RemoveNode).SubnetworkID() - require.NoError(t, err) - - require.Equal(t, rawSubnetID, gotRaw) - require.Equal(t, pub.Bytes(), e.(RemoveNode).Node()) - }) -} - -func createNotifyEventFromItems(items []stackitem.Item) *state.ContainedNotificationEvent { - return &state.ContainedNotificationEvent{ - NotificationEvent: state.NotificationEvent{ - Item: stackitem.NewArray(items), - }, - } -} diff --git a/pkg/services/control/ir/service.pb.go b/pkg/services/control/ir/service.pb.go index 9d69f51bc1..47c65adf5c 100644 --- a/pkg/services/control/ir/service.pb.go +++ b/pkg/services/control/ir/service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.21.7 +// protoc v3.21.9 // source: pkg/services/control/ir/service.proto package control diff --git a/pkg/services/control/ir/service_grpc.pb.go b/pkg/services/control/ir/service_grpc.pb.go index ac762e5fe5..052c263426 100644 --- a/pkg/services/control/ir/service_grpc.pb.go +++ b/pkg/services/control/ir/service_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.7 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v3.21.9 // source: pkg/services/control/ir/service.proto package control @@ -18,6 +18,10 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + ControlService_HealthCheck_FullMethodName = "/ircontrol.ControlService/HealthCheck" +) + // ControlServiceClient is the client API for ControlService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -36,7 +40,7 @@ func NewControlServiceClient(cc grpc.ClientConnInterface) ControlServiceClient { func (c *controlServiceClient) HealthCheck(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { out := new(HealthCheckResponse) - err := c.cc.Invoke(ctx, "/ircontrol.ControlService/HealthCheck", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_HealthCheck_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -80,7 +84,7 @@ func _ControlService_HealthCheck_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/ircontrol.ControlService/HealthCheck", + FullMethod: ControlService_HealthCheck_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).HealthCheck(ctx, req.(*HealthCheckRequest)) diff --git a/pkg/services/control/ir/types.pb.go b/pkg/services/control/ir/types.pb.go index 97de99cb69..453d28e4cd 100644 --- a/pkg/services/control/ir/types.pb.go +++ b/pkg/services/control/ir/types.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.21.7 +// protoc v3.21.9 // source: pkg/services/control/ir/types.proto package control diff --git a/pkg/services/control/service.pb.go b/pkg/services/control/service.pb.go index c18408ed7c..9130d41fab 100644 --- a/pkg/services/control/service.pb.go +++ b/pkg/services/control/service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.21.7 +// protoc v3.21.9 // source: pkg/services/control/service.proto package control diff --git a/pkg/services/control/service_grpc.pb.go b/pkg/services/control/service_grpc.pb.go index 50439cc9be..2f9ebbc2cd 100644 --- a/pkg/services/control/service_grpc.pb.go +++ b/pkg/services/control/service_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.7 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v3.21.9 // source: pkg/services/control/service.proto package control @@ -18,6 +18,19 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + ControlService_HealthCheck_FullMethodName = "/control.ControlService/HealthCheck" + ControlService_SetNetmapStatus_FullMethodName = "/control.ControlService/SetNetmapStatus" + ControlService_DropObjects_FullMethodName = "/control.ControlService/DropObjects" + ControlService_ListShards_FullMethodName = "/control.ControlService/ListShards" + ControlService_SetShardMode_FullMethodName = "/control.ControlService/SetShardMode" + ControlService_DumpShard_FullMethodName = "/control.ControlService/DumpShard" + ControlService_RestoreShard_FullMethodName = "/control.ControlService/RestoreShard" + ControlService_SynchronizeTree_FullMethodName = "/control.ControlService/SynchronizeTree" + ControlService_EvacuateShard_FullMethodName = "/control.ControlService/EvacuateShard" + ControlService_FlushCache_FullMethodName = "/control.ControlService/FlushCache" +) + // ControlServiceClient is the client API for ControlService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -54,7 +67,7 @@ func NewControlServiceClient(cc grpc.ClientConnInterface) ControlServiceClient { func (c *controlServiceClient) HealthCheck(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { out := new(HealthCheckResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/HealthCheck", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_HealthCheck_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -63,7 +76,7 @@ func (c *controlServiceClient) HealthCheck(ctx context.Context, in *HealthCheckR func (c *controlServiceClient) SetNetmapStatus(ctx context.Context, in *SetNetmapStatusRequest, opts ...grpc.CallOption) (*SetNetmapStatusResponse, error) { out := new(SetNetmapStatusResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/SetNetmapStatus", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_SetNetmapStatus_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -72,7 +85,7 @@ func (c *controlServiceClient) SetNetmapStatus(ctx context.Context, in *SetNetma func (c *controlServiceClient) DropObjects(ctx context.Context, in *DropObjectsRequest, opts ...grpc.CallOption) (*DropObjectsResponse, error) { out := new(DropObjectsResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/DropObjects", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_DropObjects_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -81,7 +94,7 @@ func (c *controlServiceClient) DropObjects(ctx context.Context, in *DropObjectsR func (c *controlServiceClient) ListShards(ctx context.Context, in *ListShardsRequest, opts ...grpc.CallOption) (*ListShardsResponse, error) { out := new(ListShardsResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/ListShards", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_ListShards_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -90,7 +103,7 @@ func (c *controlServiceClient) ListShards(ctx context.Context, in *ListShardsReq func (c *controlServiceClient) SetShardMode(ctx context.Context, in *SetShardModeRequest, opts ...grpc.CallOption) (*SetShardModeResponse, error) { out := new(SetShardModeResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/SetShardMode", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_SetShardMode_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -99,7 +112,7 @@ func (c *controlServiceClient) SetShardMode(ctx context.Context, in *SetShardMod func (c *controlServiceClient) DumpShard(ctx context.Context, in *DumpShardRequest, opts ...grpc.CallOption) (*DumpShardResponse, error) { out := new(DumpShardResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/DumpShard", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_DumpShard_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -108,7 +121,7 @@ func (c *controlServiceClient) DumpShard(ctx context.Context, in *DumpShardReque func (c *controlServiceClient) RestoreShard(ctx context.Context, in *RestoreShardRequest, opts ...grpc.CallOption) (*RestoreShardResponse, error) { out := new(RestoreShardResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/RestoreShard", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_RestoreShard_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -117,7 +130,7 @@ func (c *controlServiceClient) RestoreShard(ctx context.Context, in *RestoreShar func (c *controlServiceClient) SynchronizeTree(ctx context.Context, in *SynchronizeTreeRequest, opts ...grpc.CallOption) (*SynchronizeTreeResponse, error) { out := new(SynchronizeTreeResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/SynchronizeTree", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_SynchronizeTree_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -126,7 +139,7 @@ func (c *controlServiceClient) SynchronizeTree(ctx context.Context, in *Synchron func (c *controlServiceClient) EvacuateShard(ctx context.Context, in *EvacuateShardRequest, opts ...grpc.CallOption) (*EvacuateShardResponse, error) { out := new(EvacuateShardResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/EvacuateShard", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_EvacuateShard_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -135,7 +148,7 @@ func (c *controlServiceClient) EvacuateShard(ctx context.Context, in *EvacuateSh func (c *controlServiceClient) FlushCache(ctx context.Context, in *FlushCacheRequest, opts ...grpc.CallOption) (*FlushCacheResponse, error) { out := new(FlushCacheResponse) - err := c.cc.Invoke(ctx, "/control.ControlService/FlushCache", in, out, opts...) + err := c.cc.Invoke(ctx, ControlService_FlushCache_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -224,7 +237,7 @@ func _ControlService_HealthCheck_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/HealthCheck", + FullMethod: ControlService_HealthCheck_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).HealthCheck(ctx, req.(*HealthCheckRequest)) @@ -242,7 +255,7 @@ func _ControlService_SetNetmapStatus_Handler(srv interface{}, ctx context.Contex } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/SetNetmapStatus", + FullMethod: ControlService_SetNetmapStatus_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).SetNetmapStatus(ctx, req.(*SetNetmapStatusRequest)) @@ -260,7 +273,7 @@ func _ControlService_DropObjects_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/DropObjects", + FullMethod: ControlService_DropObjects_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).DropObjects(ctx, req.(*DropObjectsRequest)) @@ -278,7 +291,7 @@ func _ControlService_ListShards_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/ListShards", + FullMethod: ControlService_ListShards_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).ListShards(ctx, req.(*ListShardsRequest)) @@ -296,7 +309,7 @@ func _ControlService_SetShardMode_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/SetShardMode", + FullMethod: ControlService_SetShardMode_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).SetShardMode(ctx, req.(*SetShardModeRequest)) @@ -314,7 +327,7 @@ func _ControlService_DumpShard_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/DumpShard", + FullMethod: ControlService_DumpShard_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).DumpShard(ctx, req.(*DumpShardRequest)) @@ -332,7 +345,7 @@ func _ControlService_RestoreShard_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/RestoreShard", + FullMethod: ControlService_RestoreShard_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).RestoreShard(ctx, req.(*RestoreShardRequest)) @@ -350,7 +363,7 @@ func _ControlService_SynchronizeTree_Handler(srv interface{}, ctx context.Contex } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/SynchronizeTree", + FullMethod: ControlService_SynchronizeTree_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).SynchronizeTree(ctx, req.(*SynchronizeTreeRequest)) @@ -368,7 +381,7 @@ func _ControlService_EvacuateShard_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/EvacuateShard", + FullMethod: ControlService_EvacuateShard_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).EvacuateShard(ctx, req.(*EvacuateShardRequest)) @@ -386,7 +399,7 @@ func _ControlService_FlushCache_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/control.ControlService/FlushCache", + FullMethod: ControlService_FlushCache_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ControlServiceServer).FlushCache(ctx, req.(*FlushCacheRequest)) diff --git a/pkg/services/control/types.pb.go b/pkg/services/control/types.pb.go index 9fd1ca7a08..6a4133ca95 100644 --- a/pkg/services/control/types.pb.go +++ b/pkg/services/control/types.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.21.7 +// protoc v3.21.9 // source: pkg/services/control/types.proto package control @@ -569,9 +569,6 @@ func (x *BlobstorInfo) GetType() string { // attributes it's a string presenting floating point number with comma or // point delimiter for decimal part. In the Network Map it will be saved as // 64-bit unsigned integer representing number of minimal token fractions. -// - Subnet \ -// String ID of Node's storage subnet. There can be only one subnet served -// by the Storage Node. // - Locode \ // Node's geographic location in // [UN/LOCODE](https://www.unece.org/cefact/codesfortrade/codes_index.html) diff --git a/pkg/services/control/types.proto b/pkg/services/control/types.proto index 1994e24489..ec393f3e07 100644 --- a/pkg/services/control/types.proto +++ b/pkg/services/control/types.proto @@ -54,9 +54,6 @@ message NodeInfo { // attributes it's a string presenting floating point number with comma or // point delimiter for decimal part. In the Network Map it will be saved as // 64-bit unsigned integer representing number of minimal token fractions. - // * Subnet \ - // String ID of Node's storage subnet. There can be only one subnet served - // by the Storage Node. // * Locode \ // Node's geographic location in // [UN/LOCODE](https://www.unece.org/cefact/codesfortrade/codes_index.html) diff --git a/pkg/services/tree/service.pb.go b/pkg/services/tree/service.pb.go index 303fcd9499..b711ccdb94 100644 --- a/pkg/services/tree/service.pb.go +++ b/pkg/services/tree/service.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.21.7 +// protoc v3.21.9 // source: pkg/services/tree/service.proto package tree diff --git a/pkg/services/tree/service_grpc.pb.go b/pkg/services/tree/service_grpc.pb.go index aca152ad09..2c0828951a 100644 --- a/pkg/services/tree/service_grpc.pb.go +++ b/pkg/services/tree/service_grpc.pb.go @@ -1,7 +1,10 @@ +//* +// Service for working with CRDT tree. + // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.7 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v3.21.9 // source: pkg/services/tree/service.proto package tree @@ -18,6 +21,19 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + TreeService_Add_FullMethodName = "/tree.TreeService/Add" + TreeService_AddByPath_FullMethodName = "/tree.TreeService/AddByPath" + TreeService_Remove_FullMethodName = "/tree.TreeService/Remove" + TreeService_Move_FullMethodName = "/tree.TreeService/Move" + TreeService_GetNodeByPath_FullMethodName = "/tree.TreeService/GetNodeByPath" + TreeService_GetSubTree_FullMethodName = "/tree.TreeService/GetSubTree" + TreeService_TreeList_FullMethodName = "/tree.TreeService/TreeList" + TreeService_Apply_FullMethodName = "/tree.TreeService/Apply" + TreeService_GetOpLog_FullMethodName = "/tree.TreeService/GetOpLog" + TreeService_Healthcheck_FullMethodName = "/tree.TreeService/Healthcheck" +) + // TreeServiceClient is the client API for TreeService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -55,7 +71,7 @@ func NewTreeServiceClient(cc grpc.ClientConnInterface) TreeServiceClient { func (c *treeServiceClient) Add(ctx context.Context, in *AddRequest, opts ...grpc.CallOption) (*AddResponse, error) { out := new(AddResponse) - err := c.cc.Invoke(ctx, "/tree.TreeService/Add", in, out, opts...) + err := c.cc.Invoke(ctx, TreeService_Add_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -64,7 +80,7 @@ func (c *treeServiceClient) Add(ctx context.Context, in *AddRequest, opts ...grp func (c *treeServiceClient) AddByPath(ctx context.Context, in *AddByPathRequest, opts ...grpc.CallOption) (*AddByPathResponse, error) { out := new(AddByPathResponse) - err := c.cc.Invoke(ctx, "/tree.TreeService/AddByPath", in, out, opts...) + err := c.cc.Invoke(ctx, TreeService_AddByPath_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -73,7 +89,7 @@ func (c *treeServiceClient) AddByPath(ctx context.Context, in *AddByPathRequest, func (c *treeServiceClient) Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*RemoveResponse, error) { out := new(RemoveResponse) - err := c.cc.Invoke(ctx, "/tree.TreeService/Remove", in, out, opts...) + err := c.cc.Invoke(ctx, TreeService_Remove_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -82,7 +98,7 @@ func (c *treeServiceClient) Remove(ctx context.Context, in *RemoveRequest, opts func (c *treeServiceClient) Move(ctx context.Context, in *MoveRequest, opts ...grpc.CallOption) (*MoveResponse, error) { out := new(MoveResponse) - err := c.cc.Invoke(ctx, "/tree.TreeService/Move", in, out, opts...) + err := c.cc.Invoke(ctx, TreeService_Move_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -91,7 +107,7 @@ func (c *treeServiceClient) Move(ctx context.Context, in *MoveRequest, opts ...g func (c *treeServiceClient) GetNodeByPath(ctx context.Context, in *GetNodeByPathRequest, opts ...grpc.CallOption) (*GetNodeByPathResponse, error) { out := new(GetNodeByPathResponse) - err := c.cc.Invoke(ctx, "/tree.TreeService/GetNodeByPath", in, out, opts...) + err := c.cc.Invoke(ctx, TreeService_GetNodeByPath_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -99,7 +115,7 @@ func (c *treeServiceClient) GetNodeByPath(ctx context.Context, in *GetNodeByPath } func (c *treeServiceClient) GetSubTree(ctx context.Context, in *GetSubTreeRequest, opts ...grpc.CallOption) (TreeService_GetSubTreeClient, error) { - stream, err := c.cc.NewStream(ctx, &TreeService_ServiceDesc.Streams[0], "/tree.TreeService/GetSubTree", opts...) + stream, err := c.cc.NewStream(ctx, &TreeService_ServiceDesc.Streams[0], TreeService_GetSubTree_FullMethodName, opts...) if err != nil { return nil, err } @@ -132,7 +148,7 @@ func (x *treeServiceGetSubTreeClient) Recv() (*GetSubTreeResponse, error) { func (c *treeServiceClient) TreeList(ctx context.Context, in *TreeListRequest, opts ...grpc.CallOption) (*TreeListResponse, error) { out := new(TreeListResponse) - err := c.cc.Invoke(ctx, "/tree.TreeService/TreeList", in, out, opts...) + err := c.cc.Invoke(ctx, TreeService_TreeList_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -141,7 +157,7 @@ func (c *treeServiceClient) TreeList(ctx context.Context, in *TreeListRequest, o func (c *treeServiceClient) Apply(ctx context.Context, in *ApplyRequest, opts ...grpc.CallOption) (*ApplyResponse, error) { out := new(ApplyResponse) - err := c.cc.Invoke(ctx, "/tree.TreeService/Apply", in, out, opts...) + err := c.cc.Invoke(ctx, TreeService_Apply_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -149,7 +165,7 @@ func (c *treeServiceClient) Apply(ctx context.Context, in *ApplyRequest, opts .. } func (c *treeServiceClient) GetOpLog(ctx context.Context, in *GetOpLogRequest, opts ...grpc.CallOption) (TreeService_GetOpLogClient, error) { - stream, err := c.cc.NewStream(ctx, &TreeService_ServiceDesc.Streams[1], "/tree.TreeService/GetOpLog", opts...) + stream, err := c.cc.NewStream(ctx, &TreeService_ServiceDesc.Streams[1], TreeService_GetOpLog_FullMethodName, opts...) if err != nil { return nil, err } @@ -182,7 +198,7 @@ func (x *treeServiceGetOpLogClient) Recv() (*GetOpLogResponse, error) { func (c *treeServiceClient) Healthcheck(ctx context.Context, in *HealthcheckRequest, opts ...grpc.CallOption) (*HealthcheckResponse, error) { out := new(HealthcheckResponse) - err := c.cc.Invoke(ctx, "/tree.TreeService/Healthcheck", in, out, opts...) + err := c.cc.Invoke(ctx, TreeService_Healthcheck_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -272,7 +288,7 @@ func _TreeService_Add_Handler(srv interface{}, ctx context.Context, dec func(int } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/tree.TreeService/Add", + FullMethod: TreeService_Add_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TreeServiceServer).Add(ctx, req.(*AddRequest)) @@ -290,7 +306,7 @@ func _TreeService_AddByPath_Handler(srv interface{}, ctx context.Context, dec fu } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/tree.TreeService/AddByPath", + FullMethod: TreeService_AddByPath_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TreeServiceServer).AddByPath(ctx, req.(*AddByPathRequest)) @@ -308,7 +324,7 @@ func _TreeService_Remove_Handler(srv interface{}, ctx context.Context, dec func( } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/tree.TreeService/Remove", + FullMethod: TreeService_Remove_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TreeServiceServer).Remove(ctx, req.(*RemoveRequest)) @@ -326,7 +342,7 @@ func _TreeService_Move_Handler(srv interface{}, ctx context.Context, dec func(in } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/tree.TreeService/Move", + FullMethod: TreeService_Move_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TreeServiceServer).Move(ctx, req.(*MoveRequest)) @@ -344,7 +360,7 @@ func _TreeService_GetNodeByPath_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/tree.TreeService/GetNodeByPath", + FullMethod: TreeService_GetNodeByPath_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TreeServiceServer).GetNodeByPath(ctx, req.(*GetNodeByPathRequest)) @@ -383,7 +399,7 @@ func _TreeService_TreeList_Handler(srv interface{}, ctx context.Context, dec fun } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/tree.TreeService/TreeList", + FullMethod: TreeService_TreeList_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TreeServiceServer).TreeList(ctx, req.(*TreeListRequest)) @@ -401,7 +417,7 @@ func _TreeService_Apply_Handler(srv interface{}, ctx context.Context, dec func(i } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/tree.TreeService/Apply", + FullMethod: TreeService_Apply_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TreeServiceServer).Apply(ctx, req.(*ApplyRequest)) @@ -440,7 +456,7 @@ func _TreeService_Healthcheck_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/tree.TreeService/Healthcheck", + FullMethod: TreeService_Healthcheck_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TreeServiceServer).Healthcheck(ctx, req.(*HealthcheckRequest)) diff --git a/pkg/services/tree/types.pb.go b/pkg/services/tree/types.pb.go index ba35057b25..615eb95a3e 100644 --- a/pkg/services/tree/types.pb.go +++ b/pkg/services/tree/types.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.21.7 +// protoc v3.21.9 // source: pkg/services/tree/types.proto package tree diff --git a/pkg/util/precision/converter.go b/pkg/util/precision/converter.go index bd2948f0a4..a7b80468ef 100644 --- a/pkg/util/precision/converter.go +++ b/pkg/util/precision/converter.go @@ -39,11 +39,18 @@ func convert(n, factor *big.Int, decreasePrecision bool) *big.Int { // NewConverter returns Fixed8Converter. func NewConverter(precision uint32) Fixed8Converter { - var c Fixed8Converter - - c.SetBalancePrecision(precision) + exp := int(precision) - fixed8Precision + if exp < 0 { + exp = -exp + } - return c + return Fixed8Converter{ + converter: converter{ + base: fixed8Precision, + target: precision, + factor: new(big.Int).SetInt64(int64(math.Pow10(exp))), + }, + } } func (c converter) toTarget(n *big.Int) *big.Int { @@ -64,18 +71,6 @@ func (c Fixed8Converter) ToBalancePrecision(n int64) int64 { return c.toTarget(new(big.Int).SetInt64(n)).Int64() } -// SetBalancePrecision prepares converter to work. -func (c *Fixed8Converter) SetBalancePrecision(precision uint32) { - exp := int(precision) - fixed8Precision - if exp < 0 { - exp = -exp - } - - c.base = fixed8Precision - c.target = precision - c.factor = new(big.Int).SetInt64(int64(math.Pow10(exp))) -} - // Convert is a wrapper of convert function. Use cached `converter` struct // if fromPrecision and toPrecision are constant. func Convert(fromPrecision, toPrecision uint32, n *big.Int) *big.Int {