package helper import ( "errors" "fmt" "time" "git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" nns2 "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/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "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/spf13/cobra" "github.com/spf13/viper" ) var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", constants.MaxAlphabetNodes) func AwaitTx(cmd *cobra.Command, c Client, txs []HashVUBPair) error { cmd.Println("Waiting for transactions to persist...") at := trigger.Application var retErr error loop: for i := range txs { var it int var pollInterval time.Duration var pollIntervalChanged bool for { // We must fetch current height before application log, to avoid race condition. currBlock, err := c.GetBlockCount() if err != nil { return fmt.Errorf("can't fetch current block height: %w", err) } res, err := c.GetApplicationLog(txs[i].Hash, &at) if err == nil { if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vmstate.Halt { retErr = fmt.Errorf("tx %d persisted in %s state: %s", i, res.Executions[0].VMState, res.Executions[0].FaultException) } continue loop } if txs[i].Vub < currBlock { return fmt.Errorf("tx was not persisted: Vub=%d, height=%d", txs[i].Vub, currBlock) } pollInterval, pollIntervalChanged = NextPollInterval(it, pollInterval) if pollIntervalChanged && viper.GetBool(commonflags.Verbose) { cmd.Printf("Pool interval to check transaction persistence changed: %s\n", pollInterval.String()) } timer := time.NewTimer(pollInterval) select { case <-cmd.Context().Done(): return cmd.Context().Err() case <-timer.C: } it++ } } return retErr } func NextPollInterval(it int, previous time.Duration) (time.Duration, bool) { const minPollInterval = 1 * time.Second const maxPollInterval = 16 * time.Second const changeAfter = 5 if it == 0 { return minPollInterval, true } if it%changeAfter != 0 { return previous, false } nextInterval := previous * 2 if nextInterval > maxPollInterval { return maxPollInterval, previous != maxPollInterval } return nextInterval, true } func GetWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) { for i := range w.Accounts { if w.Accounts[i].Label == typ { return w.Accounts[i], nil } } return nil, fmt.Errorf("account for '%s' not found", typ) } func NNSResolve(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (stackitem.Item, error) { return unwrap.Item(inv.Call(nnsHash, "resolve", domain, int64(nns.TXT))) } // ParseNNSResolveResult parses the result of resolving NNS record. // It works with multiple formats (corresponding to multiple NNS versions). // If array of hashes is provided, it returns only the first one. func ParseNNSResolveResult(res stackitem.Item) (util.Uint160, error) { arr, ok := res.Value().([]stackitem.Item) if !ok { arr = []stackitem.Item{res} } if _, ok := res.Value().(stackitem.Null); ok || len(arr) == 0 { return util.Uint160{}, errors.New("NNS record is missing") } for i := range arr { bs, err := arr[i].TryBytes() if err != nil { continue } // We support several formats for hash encoding, this logic should be maintained in sync // with NNSResolve from pkg/morph/client/nns.go h, err := util.Uint160DecodeStringLE(string(bs)) if err == nil { return h, nil } h, err = address.StringToUint160(string(bs)) if err == nil { return h, nil } } return util.Uint160{}, errors.New("no valid hashes are found") } // NNSResolveHash Returns errMissingNNSRecord if invocation fault exception contains "token not found". func NNSResolveHash(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (util.Uint160, error) { item, err := NNSResolve(inv, nnsHash, domain) if err != nil { return util.Uint160{}, err } return ParseNNSResolveResult(item) } func DomainOf(contract string) string { return contract + ".frostfs" } func NNSResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) { res, err := NNSResolve(inv, nnsHash, domain) if err != nil { return nil, err } if _, ok := res.Value().(stackitem.Null); ok { return nil, errors.New("NNS record is missing") } arr, ok := res.Value().([]stackitem.Item) if !ok { return nil, errors.New("API of the NNS contract method `resolve` has changed") } for i := range arr { var bs []byte bs, err = arr[i].TryBytes() if err != nil { continue } return keys.NewPublicKeyFromString(string(bs)) } return nil, errors.New("no valid keys are found") } func NNSIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) { switch c.(type) { case *rpcclient.Client: inv := invoker.New(c, nil) reader := nns2.NewReader(inv, nnsHash) return reader.IsAvailable(name) default: b, err := unwrap.Bool(InvokeFunction(c, nnsHash, "isAvailable", []any{name}, nil)) if err != nil { return false, fmt.Errorf("`isAvailable`: invalid response: %w", err) } return b, nil } } func CheckNotaryEnabled(c Client) error { ns, err := c.GetNativeContracts() if err != nil { return fmt.Errorf("can't get native contract hashes: %w", err) } notaryEnabled := false nativeHashes := make(map[string]util.Uint160, len(ns)) for i := range ns { if ns[i].Manifest.Name == nativenames.Notary { notaryEnabled = true } nativeHashes[ns[i].Manifest.Name] = ns[i].Hash } if !notaryEnabled { return errors.New("notary contract must be enabled") } return nil }