forked from TrueCloudLab/frostfs-sdk-go
[#327] nns: Simplify code using neo-go v0.99.2
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
parent
2e5c66934c
commit
01c238ddc0
2 changed files with 38 additions and 101 deletions
108
ns/nns.go
108
ns/nns.go
|
@ -2,18 +2,15 @@ package ns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"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/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
|
||||||
"github.com/nspcc-dev/neofs-contract/nns"
|
"github.com/nspcc-dev/neofs-contract/nns"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
)
|
)
|
||||||
|
@ -25,31 +22,9 @@ import (
|
||||||
type NNS struct {
|
type NNS struct {
|
||||||
nnsContract util.Uint160
|
nnsContract util.Uint160
|
||||||
|
|
||||||
neoClient neoClient
|
invoker interface {
|
||||||
}
|
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
|
||||||
|
}
|
||||||
// represents virtual connection to Neo network used by NNS.Dial.
|
|
||||||
type neoClient interface {
|
|
||||||
// calls specified method of the Neo smart contract with provided parameters.
|
|
||||||
call(contract util.Uint160, method string, prm []smartcontract.Parameter) (*result.Invoke, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// implements neoClient using Neo HTTP client.
|
|
||||||
//
|
|
||||||
// note: see NNS.Dial to realize why this isn't defined as type wrapper like neoWebSocket.
|
|
||||||
type neoHTTP struct {
|
|
||||||
*rpcclient.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *neoHTTP) call(contract util.Uint160, method string, prm []smartcontract.Parameter) (*result.Invoke, error) {
|
|
||||||
return x.Client.InvokeFunction(contract, method, prm, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// implements neoClient using Neo WebSocket client.
|
|
||||||
type neoWebSocket rpcclient.WSClient
|
|
||||||
|
|
||||||
func (x *neoWebSocket) call(contract util.Uint160, method string, prm []smartcontract.Parameter) (*result.Invoke, error) {
|
|
||||||
return (*rpcclient.WSClient)(x).InvokeFunction(contract, method, prm, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial connects to the address of the NNS server. If fails, the instance
|
// Dial connects to the address of the NNS server. If fails, the instance
|
||||||
|
@ -58,39 +33,30 @@ func (x *neoWebSocket) call(contract util.Uint160, method string, prm []smartcon
|
||||||
// If URL address scheme is 'ws' or 'wss', then WebSocket protocol is used,
|
// If URL address scheme is 'ws' or 'wss', then WebSocket protocol is used,
|
||||||
// otherwise HTTP.
|
// otherwise HTTP.
|
||||||
func (n *NNS) Dial(address string) error {
|
func (n *NNS) Dial(address string) error {
|
||||||
// multiSchemeClient unites neoClient and common interface of
|
// multiSchemeClient unites invoker.RPCInvoke and common interface of
|
||||||
// neoclient.Client and neoclient.WSClient. Interface is anonymous
|
// rpcclient.Client and rpcclient.WSClient. Interface is anonymous
|
||||||
// according to assumption that common interface of these client types
|
// according to assumption that common interface of these client types
|
||||||
// is not required by design and may diverge with changes.
|
// is not required by design and may diverge with changes.
|
||||||
var multiSchemeClient interface {
|
var multiSchemeClient interface {
|
||||||
neoClient
|
invoker.RPCInvoke
|
||||||
// Init turns client to "ready-to-work" state.
|
// Init turns client to "ready-to-work" state.
|
||||||
Init() error
|
Init() error
|
||||||
// GetContractStateByID returns state of the NNS contract on 1 input.
|
// GetContractStateByID returns state of the NNS contract on 1 input.
|
||||||
GetContractStateByID(int32) (*state.Contract, error)
|
GetContractStateByID(int32) (*state.Contract, error)
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
|
|
||||||
uri, err := url.Parse(address)
|
uri, err := url.Parse(address)
|
||||||
if err == nil && (uri.Scheme == "ws" || uri.Scheme == "wss") {
|
if err == nil && (uri.Scheme == "ws" || uri.Scheme == "wss") {
|
||||||
cWebSocket, err := rpcclient.NewWS(context.Background(), address, rpcclient.Options{})
|
multiSchemeClient, err = rpcclient.NewWS(context.Background(), address, rpcclient.Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create Neo WebSocket client: %w", err)
|
return fmt.Errorf("create Neo WebSocket client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
multiSchemeClient = (*neoWebSocket)(cWebSocket)
|
|
||||||
} else {
|
} else {
|
||||||
cHTTP, err := rpcclient.New(context.Background(), address, rpcclient.Options{})
|
multiSchemeClient, err = rpcclient.New(context.Background(), address, rpcclient.Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create Neo HTTP client: %w", err)
|
return fmt.Errorf("create Neo HTTP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if neoHTTP is defined as type wrapper
|
|
||||||
// type neoHTTP neoclient.Client
|
|
||||||
// then next assignment causes compilation error
|
|
||||||
// multiSchemeClient = (*neoHTTP)(cHTTP)
|
|
||||||
multiSchemeClient = &neoHTTP{
|
|
||||||
Client: cHTTP,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = multiSchemeClient.Init(); err != nil {
|
if err = multiSchemeClient.Init(); err != nil {
|
||||||
|
@ -102,7 +68,7 @@ func (n *NNS) Dial(address string) error {
|
||||||
return fmt.Errorf("get NNS contract state: %w", err)
|
return fmt.Errorf("get NNS contract state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
n.neoClient = multiSchemeClient
|
n.invoker = invoker.New(multiSchemeClient, nil)
|
||||||
n.nnsContract = nnsContract.Hash
|
n.nnsContract = nnsContract.Hash
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -116,50 +82,24 @@ func (n *NNS) Dial(address string) error {
|
||||||
//
|
//
|
||||||
// See also https://docs.neo.org/docs/en-us/reference/nns.html.
|
// See also https://docs.neo.org/docs/en-us/reference/nns.html.
|
||||||
func (n *NNS) ResolveContainerName(name string) (cid.ID, error) {
|
func (n *NNS) ResolveContainerName(name string) (cid.ID, error) {
|
||||||
res, err := n.neoClient.call(n.nnsContract, "resolve", []smartcontract.Parameter{
|
arr, err := unwrap.Array(n.invoker.Call(n.nnsContract, "resolve",
|
||||||
{
|
name+".container", int64(nns.TXT),
|
||||||
Type: smartcontract.StringType,
|
))
|
||||||
Value: name + ".container",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: smartcontract.IntegerType,
|
|
||||||
Value: big.NewInt(int64(nns.TXT)),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cid.ID{}, fmt.Errorf("invoke NNS contract: %w", err)
|
return cid.ID{}, fmt.Errorf("contract invocation: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.State != vmstate.Halt.String() {
|
var id cid.ID
|
||||||
return cid.ID{}, fmt.Errorf("NNS contract fault exception: %s", res.FaultException)
|
|
||||||
} else if len(res.Stack) == 0 {
|
|
||||||
return cid.ID{}, errors.New("empty stack in invocation result")
|
|
||||||
}
|
|
||||||
|
|
||||||
itemArr, err := res.Stack[len(res.Stack)-1].Convert(stackitem.ArrayT) // top stack element is last in the array
|
for i := range arr {
|
||||||
if err != nil {
|
bs, err := arr[i].TryBytes()
|
||||||
return cid.ID{}, fmt.Errorf("convert stack item to %s", stackitem.ArrayT)
|
if err != nil {
|
||||||
}
|
return cid.ID{}, fmt.Errorf("convert array item to byte slice: %w", err)
|
||||||
|
|
||||||
if _, ok := itemArr.(stackitem.Null); !ok {
|
|
||||||
arr, ok := itemArr.Value().([]stackitem.Item)
|
|
||||||
if !ok {
|
|
||||||
// unexpected for types from stackitem package
|
|
||||||
return cid.ID{}, errors.New("invalid cast to stack item slice")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var id cid.ID
|
err = id.DecodeString(string(bs))
|
||||||
|
if err == nil {
|
||||||
for i := range arr {
|
return id, nil
|
||||||
bs, err := arr[i].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return cid.ID{}, fmt.Errorf("convert array item to byte slice: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = id.DecodeString(string(bs))
|
|
||||||
if err == nil {
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
"github.com/nspcc-dev/neofs-contract/nns"
|
|
||||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -30,18 +28,16 @@ type testNeoClient struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *testNeoClient) call(contract util.Uint160, method string, prm []smartcontract.Parameter) (*result.Invoke, error) {
|
func (x *testNeoClient) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||||
require.Equal(x.t, x.expectedContract, contract)
|
var domain string
|
||||||
require.Equal(x.t, "resolve", method)
|
|
||||||
require.Len(x.t, prm, 2)
|
|
||||||
require.Equal(x.t, smartcontract.StringType, prm[0].Type)
|
|
||||||
require.Equal(x.t, smartcontract.IntegerType, prm[1].Type)
|
|
||||||
require.EqualValues(x.t, big.NewInt(int64(nns.TXT)), prm[1].Value)
|
|
||||||
|
|
||||||
val, ok := prm[0].Value.(string)
|
require.Equal(x.t, x.expectedContract, contract)
|
||||||
require.True(x.t, ok)
|
require.Equal(x.t, "resolve", operation)
|
||||||
require.True(x.t, strings.HasSuffix(val, ".container"))
|
require.Len(x.t, params, 2)
|
||||||
require.NotEmpty(x.t, strings.TrimSuffix(val, ".container"))
|
require.NotPanics(x.t, func() { domain = params[0].(string) })
|
||||||
|
require.NotPanics(x.t, func() { _ = params[1].(int64) })
|
||||||
|
require.True(x.t, strings.HasSuffix(domain, ".container"))
|
||||||
|
require.NotEmpty(x.t, strings.TrimSuffix(domain, ".container"))
|
||||||
|
|
||||||
return &x.res, x.err
|
return &x.res, x.err
|
||||||
}
|
}
|
||||||
|
@ -78,14 +74,15 @@ func TestNNS_ResolveContainerName(t *testing.T) {
|
||||||
|
|
||||||
n := NNS{
|
n := NNS{
|
||||||
nnsContract: nnsContract,
|
nnsContract: nnsContract,
|
||||||
neoClient: testC,
|
invoker: testC,
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("invocation failure", func(t *testing.T) {
|
t.Run("invocation failure", func(t *testing.T) {
|
||||||
testC.err = errors.New("invoke err")
|
err1 := errors.New("invoke err")
|
||||||
|
testC.err = err1
|
||||||
|
|
||||||
_, err := n.ResolveContainerName(testContainerName)
|
_, err2 := n.ResolveContainerName(testContainerName)
|
||||||
require.Error(t, err)
|
require.ErrorIs(t, err2, err1)
|
||||||
})
|
})
|
||||||
|
|
||||||
testC.err = nil
|
testC.err = nil
|
||||||
|
|
Loading…
Reference in a new issue