lego/challenge/nns01/nns_provider.go

172 lines
4.9 KiB
Go
Raw Normal View History

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 len(accountAddress) == 0 {
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
}