2022-04-12 08:25:18 +00:00
|
|
|
package ns
|
2021-12-10 11:50:10 +00:00
|
|
|
|
|
|
|
import (
|
2022-03-18 08:32:00 +00:00
|
|
|
"context"
|
|
|
|
"errors"
|
2021-12-10 11:50:10 +00:00
|
|
|
"fmt"
|
|
|
|
|
|
|
|
nns "github.com/nspcc-dev/neo-go/examples/nft-nd-nns"
|
2022-03-18 08:32:00 +00:00
|
|
|
neoclient "github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
2021-12-10 11:50:10 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
|
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
|
|
|
)
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
// NNS looks up NeoFS names using Neo Name Service.
|
|
|
|
//
|
|
|
|
// Instances are created with a variable declaration. Before work, the connection
|
|
|
|
// to the NNS server MUST BE established using Dial method.
|
|
|
|
type NNS struct {
|
|
|
|
nnsContract util.Uint160
|
|
|
|
|
|
|
|
// neoclient.Client interface wrapper, needed for testing
|
|
|
|
neoClient interface {
|
|
|
|
invoke(contract util.Uint160, method string, prm []smartcontract.Parameter) (*result.Invoke, error)
|
|
|
|
}
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
// client is a core implementation of internal NNS.neoClient which is used by NNS.Dial.
|
2022-04-12 08:19:32 +00:00
|
|
|
type client neoclient.WSClient
|
2022-03-18 08:32:00 +00:00
|
|
|
|
|
|
|
func (x *client) invoke(contract util.Uint160, method string, prm []smartcontract.Parameter) (*result.Invoke, error) {
|
2022-04-12 08:19:32 +00:00
|
|
|
return (*neoclient.WSClient)(x).InvokeFunction(contract, method, prm, nil)
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
// Dial connects to the address of the NNS server. If fails, the instance
|
|
|
|
// SHOULD NOT be used.
|
|
|
|
func (n *NNS) Dial(address string) error {
|
2022-04-12 08:19:32 +00:00
|
|
|
cli, err := neoclient.NewWS(context.Background(), address, neoclient.Options{})
|
2022-03-18 08:32:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("create neo client: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = cli.Init(); err != nil {
|
|
|
|
return fmt.Errorf("initialize neo client: %w", err)
|
|
|
|
}
|
2021-12-10 11:50:10 +00:00
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
nnsContract, err := cli.GetContractStateByID(1)
|
2021-12-10 11:50:10 +00:00
|
|
|
if err != nil {
|
2022-03-18 08:32:00 +00:00
|
|
|
return fmt.Errorf("get NNS contract state: %w", err)
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
n.neoClient = (*client)(cli)
|
|
|
|
n.nnsContract = nnsContract.Hash
|
|
|
|
|
|
|
|
return nil
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
// ResolveContainerName looks up for NNS TXT records for the given container name
|
|
|
|
// by calling `resolve` method of NNS contract. Returns the first record which represents
|
|
|
|
// valid container ID in a string format. Otherwise, returns an error.
|
|
|
|
//
|
|
|
|
// ResolveContainerName MUST NOT be called before successful Dial.
|
|
|
|
//
|
|
|
|
// See also https://docs.neo.org/docs/en-us/reference/nns.html.
|
|
|
|
func (n *NNS) ResolveContainerName(name string) (*cid.ID, error) {
|
|
|
|
res, err := n.neoClient.invoke(n.nnsContract, "resolve", []smartcontract.Parameter{
|
2021-12-10 11:50:10 +00:00
|
|
|
{
|
|
|
|
Type: smartcontract.StringType,
|
2022-03-18 08:32:00 +00:00
|
|
|
Value: name + ".container",
|
2021-12-10 11:50:10 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: smartcontract.IntegerType,
|
|
|
|
Value: int64(nns.TXT),
|
|
|
|
},
|
2022-03-18 08:32:00 +00:00
|
|
|
})
|
2021-12-10 11:50:10 +00:00
|
|
|
if err != nil {
|
2022-03-18 08:32:00 +00:00
|
|
|
return nil, fmt.Errorf("invoke NNS contract: %w", err)
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
if res.State != vm.HaltState.String() {
|
|
|
|
return nil, fmt.Errorf("NNS contract fault exception: %s", res.FaultException)
|
|
|
|
} else if len(res.Stack) == 0 {
|
|
|
|
return nil, errors.New("empty stack in invocation result")
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
itemArr, err := res.Stack[len(res.Stack)-1].Convert(stackitem.ArrayT) // top stack element is last in the array
|
2021-12-10 11:50:10 +00:00
|
|
|
if err != nil {
|
2022-03-18 08:32:00 +00:00
|
|
|
return nil, fmt.Errorf("convert stack item to %s", stackitem.ArrayT)
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
if _, ok := itemArr.(stackitem.Null); !ok {
|
|
|
|
arr, ok := itemArr.Value().([]stackitem.Item)
|
|
|
|
if !ok {
|
|
|
|
// unexpected for types from stackitem package
|
|
|
|
return nil, errors.New("invalid cast to stack item slice")
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
var id cid.ID
|
2021-12-10 11:50:10 +00:00
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
for i := range arr {
|
|
|
|
bs, err := arr[i].TryBytes()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("convert array item to byte slice: %w", err)
|
|
|
|
}
|
2021-12-10 11:50:10 +00:00
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
err = id.Parse(string(bs))
|
|
|
|
if err == nil {
|
|
|
|
return &id, nil
|
|
|
|
}
|
|
|
|
}
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 08:32:00 +00:00
|
|
|
return nil, errNotFound
|
2021-12-10 11:50:10 +00:00
|
|
|
}
|