172 lines
4.9 KiB
Go
172 lines
4.9 KiB
Go
|
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
|
||
|
}
|