package acme import ( "context" "errors" "fmt" "net/url" "git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // multiSchemeClient unites invoker.RPCInvoke and common interface of // rpcclient.Client and rpcclient.WSClient. type multiSchemeClient interface { invoker.RPCInvoke // Init turns client to "ready-to-work" state. Init() error // Close closes connections. Close() // GetContractStateByID returns state of the NNS contract on 1 input. GetContractStateByID(int32) (*state.Contract, error) } // NNS is used to interact with NNS contract. // Before work, the connection to the NNS server must be established using Dial method. type NNS struct { nnsContract util.Uint160 client multiSchemeClient } // NNSContext is used to store info about NNS server. type NNSContext struct { nnsServer string } type nnsKey struct{} // NewNNSContext adds new NNSContext with given params to the context. func NewNNSContext(ctx context.Context, nnsServer string) context.Context { return context.WithValue(ctx, nnsKey{}, NNSContext{nnsServer: nnsServer}) } // GetNNSContext returns NNSContext from the given context. func GetNNSContext(ctx context.Context) (NNSContext, bool) { c, ok := ctx.Value(nnsKey{}).(NNSContext) return c, ok } // Dial connects to the address of the NNS server. // If URL address scheme is 'ws' or 'wss', then WebSocket protocol is used, otherwise HTTP. func (n *NNS) Dial(address string) error { var err error uri, err := url.Parse(address) if err == nil && (uri.Scheme == "ws" || uri.Scheme == "wss") { n.client, err = rpcclient.NewWS(context.Background(), address, rpcclient.WSOptions{}) if err != nil { return fmt.Errorf("create Neo WebSocket client: %w", err) } } else { n.client, err = rpcclient.New(context.Background(), address, rpcclient.Options{}) if err != nil { return fmt.Errorf("create Neo HTTP client: %w", err) } } if err = n.client.Init(); err != nil { return fmt.Errorf("initialize Neo client: %w", err) } nnsContract, err := n.client.GetContractStateByID(1) if err != nil { return fmt.Errorf("get NNS contract state: %w", err) } n.nnsContract = nnsContract.Hash return nil } // Close closes connections of multiSchemeClient. func (n *NNS) Close() { n.client.Close() } // GetTXTRecords returns TXT records of the provided domain by calling `getRecords` method of NNS contract. func (n *NNS) GetTXTRecords(name string) ([]string, error) { params, err := smartcontract.NewParametersFromValues(name, int64(nns.TXT)) if err != nil { return make([]string, 0), fmt.Errorf("create slice of params: %w", err) } item, err := unwrap.Item(n.client.InvokeFunction(n.nnsContract, "getRecords", params, nil)) if err != nil { return make([]string, 0), fmt.Errorf("contract invocation: %w", err) } if _, ok := item.(stackitem.Null); !ok { arr, ok := item.Value().([]stackitem.Item) if !ok { return make([]string, 0), errors.New("invalid cast to stack item slice") } var result = make([]string, 0, len(arr)) for i := range arr { recordValue, err := arr[i].TryBytes() if err != nil { return make([]string, 0), fmt.Errorf("convert array item to byte slice: %w", err) } result = append(result, string(recordValue)) } return result, nil } return make([]string, 0), errors.New("records not found") }