From fef4f6d155509fe76d203a8fee7b8e75cc197c1b Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 1 Sep 2022 14:07:34 +0300 Subject: [PATCH] [#1748] neofs-adm: Allow to dump hashes from a custom zone Signed-off-by: Evgenii Stratonikov --- .../internal/modules/morph/dump_hashes.go | 144 +++++++++++++++--- cmd/neofs-adm/internal/modules/morph/root.go | 1 + 2 files changed, 121 insertions(+), 24 deletions(-) diff --git a/cmd/neofs-adm/internal/modules/morph/dump_hashes.go b/cmd/neofs-adm/internal/modules/morph/dump_hashes.go index 9fa92a598..349dd1bbb 100644 --- a/cmd/neofs-adm/internal/modules/morph/dump_hashes.go +++ b/cmd/neofs-adm/internal/modules/morph/dump_hashes.go @@ -3,14 +3,21 @@ package morph import ( "bytes" "fmt" + "strings" "text/tabwriter" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "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/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "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/vm/vmstate" + "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neofs-contract/nns" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -35,6 +42,11 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error { return err } + zone, _ := cmd.Flags().GetString(customZoneFlag) + if zone != "" { + return dumpCustomZoneHashes(cmd, cs.Hash, zone, c) + } + infos := []contractDumpInfo{{name: nnsContract, hash: cs.Hash}} irSize := 0 @@ -47,7 +59,6 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error { } } - buf := bytes.NewBuffer(nil) bw := io.NewBufBinWriter() if irSize != 0 { @@ -91,37 +102,65 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error { infos = append(infos, info) } - bw.Reset() - for i := range infos { - if infos[i].hash.Equals(util.Uint160{}) { - emit.Int(bw.BinWriter, 0) - } else { - emit.AppCall(bw.BinWriter, infos[i].hash, "version", callflag.NoneFlag) - } - } + fillContractVersion(cmd, c, infos) + printContractInfo(cmd, infos) - res, err := c.InvokeScript(bw.Bytes(), nil) + return nil +} + +func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string, c Client) error { + const nnsMaxTokens = 100 + + // The actual signer is not important here. + account, err := wallet.NewAccount() if err != nil { - return fmt.Errorf("can't fetch info from NNS: %w", err) + return fmt.Errorf("can't create a temporary account: %w", err) } - if res.State == vmstate.Halt.String() { - for i := range res.Stack { - infos[i].version = parseContractVersion(res.Stack[i]) + signers := []actor.SignerAccount{{ + Signer: transaction.Signer{ + Account: account.PrivateKey().GetScriptHash(), + }, + Account: account, + }} + a, err := actor.New(c.(*rpcclient.Client), signers) + if err != nil { + return fmt.Errorf("can't get a list of NNS domains: %w", err) + } + + arr, err := unwrap.Array(a.CallAndExpandIterator(nnsHash, "tokens", nnsMaxTokens)) + if err != nil { + return fmt.Errorf("can't get a list of NNS domains: %w", err) + } + + if !strings.HasPrefix(zone, ".") { + zone = "." + zone + } + + var infos []contractDumpInfo + for i := range arr { + bs, err := arr[i].TryBytes() + if err != nil { + continue } - } - tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0) - for _, info := range infos { - if info.version == "" { - info.version = "unknown" + if !bytes.HasSuffix(bs, []byte(zone)) { + continue } - _, _ = tw.Write([]byte(fmt.Sprintf("%s\t(%s):\t%s\n", - info.name, info.version, info.hash.StringLE()))) - } - _ = tw.Flush() - cmd.Print(buf.String()) + h, err := nnsResolveHash(c, nnsHash, string(bs)) + if err != nil { + continue + } + + infos = append(infos, contractDumpInfo{ + hash: h, + name: strings.TrimSuffix(string(bs), zone), + }) + } + + fillContractVersion(cmd, c, infos) + printContractInfo(cmd, infos) return nil } @@ -138,3 +177,60 @@ func parseContractVersion(item stackitem.Item) string { patch := v % 1_000 return fmt.Sprintf("v%d.%d.%d", major, minor, patch) } + +func printContractInfo(cmd *cobra.Command, infos []contractDumpInfo) { + if len(infos) == 0 { + return + } + + buf := bytes.NewBuffer(nil) + tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0) + for _, info := range infos { + if info.version == "" { + info.version = "unknown" + } + _, _ = tw.Write([]byte(fmt.Sprintf("%s\t(%s):\t%s\n", + info.name, info.version, info.hash.StringLE()))) + } + _ = tw.Flush() + + cmd.Print(buf.String()) +} + +func fillContractVersion(cmd *cobra.Command, c Client, infos []contractDumpInfo) { + bw := io.NewBufBinWriter() + sub := io.NewBufBinWriter() + for i := range infos { + if infos[i].hash.Equals(util.Uint160{}) { + emit.Int(bw.BinWriter, 0) + } else { + sub.Reset() + emit.AppCall(sub.BinWriter, infos[i].hash, "version", callflag.NoneFlag) + if sub.Err != nil { + panic(fmt.Errorf("BUG: can't create version script: %w", bw.Err)) + } + + script := sub.Bytes() + emit.Instruction(bw.BinWriter, opcode.TRY, []byte{byte(3 + len(script) + 2), 0}) + bw.BinWriter.WriteBytes(script) + emit.Instruction(bw.BinWriter, opcode.ENDTRY, []byte{2 + 1}) + emit.Opcodes(bw.BinWriter, opcode.PUSH0) + } + } + emit.Opcodes(bw.BinWriter, opcode.NOP) // for the last ENDTRY target + if bw.Err != nil { + panic(fmt.Errorf("BUG: can't create version script: %w", bw.Err)) + } + + res, err := c.InvokeScript(bw.Bytes(), nil) + if err != nil { + cmd.Printf("Can't fetch version from NNS: %v\n", err) + return + } + + if res.State == vmstate.Halt.String() { + for i := range res.Stack { + infos[i].version = parseContractVersion(res.Stack[i]) + } + } +} diff --git a/cmd/neofs-adm/internal/modules/morph/root.go b/cmd/neofs-adm/internal/modules/morph/root.go index dd1dec03f..3a354123d 100644 --- a/cmd/neofs-adm/internal/modules/morph/root.go +++ b/cmd/neofs-adm/internal/modules/morph/root.go @@ -258,6 +258,7 @@ func init() { RootCmd.AddCommand(dumpContractHashesCmd) dumpContractHashesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.") RootCmd.AddCommand(dumpNetworkConfigCmd) dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")