certificates/acme/nns.go
Marina Biryukova b13ba41053 [#2] Add NNS context
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2023-08-03 15:46:59 +03:00

122 lines
3.6 KiB
Go

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")
}