package nns01 import ( "context" "fmt" "net/url" "git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "github.com/nspcc-dev/neo-go/cli/flags" "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/actor" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" ) // multiSchemeClient unites invoker.RPCInvoke and common interface of // rpcclient.Client and rpcclient.WSClient. type multiSchemeClient interface { actor.RPCActor actor.RPCPollingWaiter // 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) } type NNSProvider struct { nnsServer string account *wallet.Account nnsContract util.Uint160 client multiSchemeClient } // NewNNSProvider returns configured NNSProvider instance. func NewNNSProvider(nnsServer string, walletFile string, accountAddress string, accountPassword string) (*NNSProvider, error) { w, err := wallet.NewWalletFromFile(walletFile) if err != nil { return nil, fmt.Errorf("retrieve wallet from file: %w", err) } var address util.Uint160 if accountAddress == "" { address = w.GetChangeAddress() } else { address, err = flags.ParseAddress(accountAddress) if err != nil { return nil, fmt.Errorf("parse account address: %w", err) } } acc := w.GetAccount(address) err = acc.Decrypt(accountPassword, w.Scrypt) if err != nil { return nil, fmt.Errorf("decrypt account: %w", err) } provider := NNSProvider{ nnsServer: nnsServer, account: acc, } return &provider, nil } // 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 *NNSProvider) dial() error { var err error uri, err := url.Parse(n.nnsServer) if err == nil && (uri.Scheme == "ws" || uri.Scheme == "wss") { // WSOptions not in package `github.com/nspcc-dev/neo-go v0.101.3` n.client, err = rpcclient.NewWS(context.Background(), n.nnsServer, rpcclient.Options{}) if err != nil { return fmt.Errorf("create Neo WebSocket client: %w", err) } } else { n.client, err = rpcclient.New(context.Background(), n.nnsServer, 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 *NNSProvider) close() { if n.client != nil { n.client.Close() } } // Present creates a TXT record using the specified parameters to fulfill the nns-01 challenge. // It implements Provider interface in order to use NNSProvider as Provider. func (n *NNSProvider) Present(domain, _, keyAuth string) error { err := n.dial() if err != nil { return fmt.Errorf("connect to the NNS server: %w", err) } act, err := actor.NewSimple(n.client, n.account) if err != nil { return fmt.Errorf("create actor: %w", err) } info := GetRecordInfo(domain, keyAuth) err = n.addTXTRecord(act, info.FQDN, info.Value) if err != nil { return fmt.Errorf("add txt record: %w", err) } return nil } // addTXTRecord adds a new TXT record with the specified data to the provided domain by calling `addRecord` method // of NNS contract. func (n *NNSProvider) addTXTRecord(act *actor.Actor, name string, data string) error { waiter, err := actor.NewPollingWaiter(n.client) if err != nil { return fmt.Errorf("waiter creation: %w", err) } _, err = waiter.Wait(act.SendCall(n.nnsContract, "addRecord", name, int64(nns.TXT), data)) if err != nil { return fmt.Errorf("contract invocation: %w", err) } return nil } // CleanUp removes the TXT record matching the specified parameters. // It implements Provider interface in order to use NNSProvider as Provider. func (n *NNSProvider) CleanUp(domain, _, keyAuth string) error { defer n.close() act, err := actor.NewSimple(n.client, n.account) if err != nil { return fmt.Errorf("create actor: %w", err) } info := GetRecordInfo(domain, keyAuth) err = n.deleteTXTRecords(act, info.FQDN) if err != nil { return fmt.Errorf("delete txt records: %w", err) } return nil } // deleteTXTRecords removes TXT records from the provided domain by calling `deleteRecords` method of NNS contract. func (n *NNSProvider) deleteTXTRecords(act *actor.Actor, name string) error { waiter, err := actor.NewPollingWaiter(n.client) if err != nil { return fmt.Errorf("waiter creation: %w", err) } _, err = waiter.Wait(act.SendCall(n.nnsContract, "deleteRecords", name, int64(nns.TXT))) if err != nil { return fmt.Errorf("contract invocation: %w", err) } return nil }