package morph import ( "errors" "fmt" "sort" frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/spf13/cobra" "github.com/spf13/viper" ) const ( namespaceFlag = "namespace" subjectNameFlag = "subject-name" subjectKeyFlag = "subject-key" subjectAddressFlag = "subject-address" includeNamesFlag = "include-names" groupNameFlag = "group-name" groupIDFlag = "group-id" ) var ( frostfsidCmd = &cobra.Command{ Use: "frostfsid", Short: "Section for frostfsid interactions commands", } frostfsidCreateNamespaceCmd = &cobra.Command{ Use: "create-namespace", Short: "Create new namespace in frostfsid contract", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidCreateNamespace, } frostfsidListNamespacesCmd = &cobra.Command{ Use: "list-namespaces", Short: "List all namespaces in frostfsid", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidListNamespaces, } frostfsidCreateSubjectCmd = &cobra.Command{ Use: "create-subject", Short: "Create subject in frostfsid contract", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidCreateSubject, } frostfsidDeleteSubjectCmd = &cobra.Command{ Use: "delete-subject", Short: "Delete subject from frostfsid contract", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidDeleteSubject, } frostfsidListSubjectsCmd = &cobra.Command{ Use: "list-subjects", Short: "List subjects in namespace", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidListSubjects, } frostfsidCreateGroupCmd = &cobra.Command{ Use: "create-group", Short: "Create group in frostfsid contract", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidCreateGroup, } frostfsidDeleteGroupCmd = &cobra.Command{ Use: "delete-group", Short: "Delete group from frostfsid contract", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidDeleteGroup, } frostfsidListGroupsCmd = &cobra.Command{ Use: "list-groups", Short: "List groups in namespace", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidListGroups, } frostfsidAddSubjectToGroupCmd = &cobra.Command{ Use: "add-subject-to-group", Short: "Add subject to group", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidAddSubjectToGroup, } frostfsidRemoveSubjectFromGroupCmd = &cobra.Command{ Use: "remove-subject-from-group", Short: "Remove subject from group", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidRemoveSubjectFromGroup, } frostfsidListGroupSubjectsCmd = &cobra.Command{ Use: "list-group-subjects", Short: "List subjects in group", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) }, Run: frostfsidListGroupSubjects, } ) func initFrostfsIDCreateNamespaceCmd() { frostfsidCmd.AddCommand(frostfsidCreateNamespaceCmd) frostfsidCreateNamespaceCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidCreateNamespaceCmd.Flags().String(namespaceFlag, "", "Namespace name to create") } func initFrostfsIDListNamespacesCmd() { frostfsidCmd.AddCommand(frostfsidListNamespacesCmd) frostfsidListNamespacesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) } func initFrostfsIDCreateSubjectCmd() { frostfsidCmd.AddCommand(frostfsidCreateSubjectCmd) frostfsidCreateSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidCreateSubjectCmd.Flags().String(namespaceFlag, "", "Namespace where create subject") frostfsidCreateSubjectCmd.Flags().String(subjectNameFlag, "", "Subject name, must be unique in namespace") frostfsidCreateSubjectCmd.Flags().String(subjectKeyFlag, "", "Subject hex-encoded public key") } func initFrostfsIDDeleteSubjectCmd() { frostfsidCmd.AddCommand(frostfsidDeleteSubjectCmd) frostfsidDeleteSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidDeleteSubjectCmd.Flags().String(subjectAddressFlag, "", "Subject address") } func initFrostfsIDListSubjectsCmd() { frostfsidCmd.AddCommand(frostfsidListSubjectsCmd) frostfsidListSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects") frostfsidListSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)") } func initFrostfsIDCreateGroupCmd() { frostfsidCmd.AddCommand(frostfsidCreateGroupCmd) frostfsidCreateGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidCreateGroupCmd.Flags().String(namespaceFlag, "", "Namespace where create group") frostfsidCreateGroupCmd.Flags().String(groupNameFlag, "", "Group name, must be unique in namespace") } func initFrostfsIDDeleteGroupCmd() { frostfsidCmd.AddCommand(frostfsidDeleteGroupCmd) frostfsidDeleteGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidDeleteGroupCmd.Flags().String(namespaceFlag, "", "Namespace to delete group") frostfsidDeleteGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id") } func initFrostfsIDListGroupsCmd() { frostfsidCmd.AddCommand(frostfsidListGroupsCmd) frostfsidListGroupsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidListGroupsCmd.Flags().String(namespaceFlag, "", "Namespace to list groups") } func initFrostfsIDAddSubjectToGroupCmd() { frostfsidCmd.AddCommand(frostfsidAddSubjectToGroupCmd) frostfsidAddSubjectToGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidAddSubjectToGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address") frostfsidAddSubjectToGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id") } func initFrostfsIDRemoveSubjectFromGroupCmd() { frostfsidCmd.AddCommand(frostfsidRemoveSubjectFromGroupCmd) frostfsidRemoveSubjectFromGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidRemoveSubjectFromGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address") frostfsidRemoveSubjectFromGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id") } func initFrostfsIDListGroupSubjectsCmd() { frostfsidCmd.AddCommand(frostfsidListGroupSubjectsCmd) frostfsidListGroupSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name") frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id") frostfsidListGroupSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)") } func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) { ns, _ := cmd.Flags().GetString(namespaceFlag) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract client: %w", err) ffsid.addCall(ffsid.roCli.CreateNamespaceCall(ns)) err = ffsid.sendWait() commonCmd.ExitOnErr(cmd, "create namespace error: %w", err) } func frostfsidListNamespaces(cmd *cobra.Command, _ []string) { ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract invoker: %w", err) namespaces, err := ffsid.roCli.ListNamespaces() commonCmd.ExitOnErr(cmd, "list namespaces: %w", err) sort.Slice(namespaces, func(i, j int) bool { return namespaces[i].Name < namespaces[j].Name }) for _, namespace := range namespaces { cmd.Printf("%q\n", namespace.Name) } } func frostfsidCreateSubject(cmd *cobra.Command, _ []string) { ns, _ := cmd.Flags().GetString(namespaceFlag) subjName, _ := cmd.Flags().GetString(subjectNameFlag) subjKey := getFrostfsIDSubjectKey(cmd) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract client: %w", err) ffsid.addCall(ffsid.roCli.CreateSubjectCall(ns, subjKey)) if subjName != "" { ffsid.addCall(ffsid.roCli.SetSubjectNameCall(subjKey.GetScriptHash(), subjName)) } err = ffsid.sendWait() commonCmd.ExitOnErr(cmd, "create subject: %w", err) } func frostfsidDeleteSubject(cmd *cobra.Command, _ []string) { subjectAddress := getFrostfsIDSubjectAddress(cmd) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract client: %w", err) ffsid.addCall(ffsid.roCli.DeleteSubjectCall(subjectAddress)) err = ffsid.sendWait() commonCmd.ExitOnErr(cmd, "delete subject error: %w", err) } func frostfsidListSubjects(cmd *cobra.Command, _ []string) { ns, _ := cmd.Flags().GetString(namespaceFlag) includeNames, _ := cmd.Flags().GetBool(includeNamesFlag) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract invoker: %w", err) subAddresses, err := ffsid.roCli.ListNamespaceSubjects(ns) commonCmd.ExitOnErr(cmd, "list subjects: %w", err) sort.Slice(subAddresses, func(i, j int) bool { return subAddresses[i].Less(subAddresses[j]) }) for _, addr := range subAddresses { if !includeNames { cmd.Println(address.Uint160ToString(addr)) continue } subj, err := ffsid.roCli.GetSubject(addr) commonCmd.ExitOnErr(cmd, "get subject: %w", err) cmd.Printf("%s (%s)\n", address.Uint160ToString(addr), subj.Name) } } func frostfsidCreateGroup(cmd *cobra.Command, _ []string) { ns, _ := cmd.Flags().GetString(namespaceFlag) groupName, _ := cmd.Flags().GetString(groupNameFlag) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract client: %w", err) ffsid.addCall(ffsid.roCli.CreateGroupCall(ns, groupName)) groupID, err := ffsid.roCli.ParseGroupID(ffsid.sendWaitRes()) commonCmd.ExitOnErr(cmd, "create group: %w", err) cmd.Printf("group '%s' created with id: %d\n", groupName, groupID) } func frostfsidDeleteGroup(cmd *cobra.Command, _ []string) { ns, _ := cmd.Flags().GetString(namespaceFlag) groupID, _ := cmd.Flags().GetInt64(groupIDFlag) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract client: %w", err) ffsid.addCall(ffsid.roCli.DeleteGroupCall(ns, groupID)) err = ffsid.sendWait() commonCmd.ExitOnErr(cmd, "delete group error: %w", err) } func frostfsidListGroups(cmd *cobra.Command, _ []string) { ns, _ := cmd.Flags().GetString(namespaceFlag) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract invoker: %w", err) groups, err := ffsid.roCli.ListGroups(ns) commonCmd.ExitOnErr(cmd, "list groups: %w", err) sort.Slice(groups, func(i, j int) bool { return groups[i].Name < groups[j].Name }) for _, group := range groups { cmd.Printf("%q (%d)\n", group.Name, group.ID) } } func frostfsidAddSubjectToGroup(cmd *cobra.Command, _ []string) { subjectAddress := getFrostfsIDSubjectAddress(cmd) groupID, _ := cmd.Flags().GetInt64(groupIDFlag) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract client: %w", err) ffsid.addCall(ffsid.roCli.AddSubjectToGroupCall(subjectAddress, groupID)) err = ffsid.sendWait() commonCmd.ExitOnErr(cmd, "add subject to group error: %w", err) } func frostfsidRemoveSubjectFromGroup(cmd *cobra.Command, _ []string) { subjectAddress := getFrostfsIDSubjectAddress(cmd) groupID, _ := cmd.Flags().GetInt64(groupIDFlag) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract client: %w", err) ffsid.addCall(ffsid.roCli.RemoveSubjectFromGroupCall(subjectAddress, groupID)) err = ffsid.sendWait() commonCmd.ExitOnErr(cmd, "remove subject from group error: %w", err) } func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) { ns, _ := cmd.Flags().GetString(namespaceFlag) groupID, _ := cmd.Flags().GetInt64(groupIDFlag) includeNames, _ := cmd.Flags().GetBool(includeNamesFlag) ffsid, err := newFrostfsIDClient(cmd) commonCmd.ExitOnErr(cmd, "init contract client: %w", err) subjects, err := ffsid.roCli.ListGroupSubjects(ns, groupID) commonCmd.ExitOnErr(cmd, "list group subjects: %w", err) sort.Slice(subjects, func(i, j int) bool { return subjects[i].Less(subjects[j]) }) for _, subjAddr := range subjects { if !includeNames { cmd.Println(address.Uint160ToString(subjAddr)) continue } subj, err := ffsid.roCli.GetSubject(subjAddr) commonCmd.ExitOnErr(cmd, "get subject: %w", err) cmd.Printf("%s (%s)\n", address.Uint160ToString(subjAddr), subj.Name) } } type frostfsidClient struct { bw *io.BufBinWriter contractHash util.Uint160 roCli *frostfsidclient.Client // client can be used only for waiting tx, parsing and forming method params wCtx *initializeContext } func newFrostfsIDClient(cmd *cobra.Command) (*frostfsidClient, error) { wCtx, err := newInitializeContext(cmd, viper.GetViper()) if err != nil { return nil, fmt.Errorf("can't to initialize context: %w", err) } r := management.NewReader(wCtx.ReadOnlyInvoker) cs, err := r.GetContractByID(1) if err != nil { return nil, fmt.Errorf("can't get NNS contract info: %w", err) } ffsidHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, domainOf(frostfsIDContract)) if err != nil { return nil, fmt.Errorf("can't get proxy contract hash: %w", err) } return &frostfsidClient{ bw: io.NewBufBinWriter(), contractHash: ffsidHash, roCli: frostfsidclient.NewSimple(wCtx.CommitteeAct, ffsidHash), wCtx: wCtx, }, nil } func (f *frostfsidClient) addCall(method string, args []any) { emit.AppCall(f.bw.BinWriter, f.contractHash, method, callflag.All, args...) } func (f *frostfsidClient) sendWait() error { if err := f.wCtx.sendConsensusTx(f.bw.Bytes()); err != nil { return err } f.bw.Reset() return f.wCtx.awaitTx() } func (f *frostfsidClient) sendWaitRes() (*state.AppExecResult, error) { if err := f.wCtx.sendConsensusTx(f.bw.Bytes()); err != nil { return nil, err } f.bw.Reset() if len(f.wCtx.SentTxs) == 0 { return nil, errors.New("no transactions to wait") } f.wCtx.Command.Println("Waiting for transactions to persist...") return f.roCli.Wait(f.wCtx.SentTxs[0].hash, f.wCtx.SentTxs[0].vub, nil) }