forked from TrueCloudLab/frostfs-node
211 lines
6.1 KiB
Go
211 lines
6.1 KiB
Go
package util
|
|
|
|
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
|
|
}
|