diff --git a/CHANGELOG.md b/CHANGELOG.md index 11a682281be..62cca7fff8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changelog for NeoFS Node ### Added - Embedded Neo contracts in `contracts` dir (#2391) +- `dump-names` command for adm ### Fixed diff --git a/cmd/neofs-adm/internal/modules/morph/dump_hashes.go b/cmd/neofs-adm/internal/modules/morph/dump_hashes.go index 3378d8a9676..139cd7c9500 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 00000000000..eb4567b0404 --- /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", string(toks[i]), 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/root.go b/cmd/neofs-adm/internal/modules/morph/root.go index a33a99ef768..94f51c9a273 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", @@ -271,6 +280,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") diff --git a/docs/cli-adm.md b/docs/cli-adm.md index a80d114e237..c898ca02e2e 100644 --- a/docs/cli-adm.md +++ b/docs/cli-adm.md @@ -70,6 +70,8 @@ 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