[#327] nns: Simplify code using neo-go v0.99.2

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
Pavel Karpy 2022-08-31 15:39:28 +03:00 committed by LeL
parent 2e5c66934c
commit 01c238ddc0
2 changed files with 38 additions and 101 deletions

View file

@ -2,18 +2,15 @@ package ns
import (
"context"
"errors"
"fmt"
"math/big"
"net/url"
"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/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/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neofs-contract/nns"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
)
@ -25,31 +22,9 @@ import (
type NNS struct {
nnsContract util.Uint160
neoClient neoClient
}
// 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)
invoker interface {
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
}
}
// 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,
// otherwise HTTP.
func (n *NNS) Dial(address string) error {
// multiSchemeClient unites neoClient and common interface of
// neoclient.Client and neoclient.WSClient. Interface is anonymous
// multiSchemeClient unites invoker.RPCInvoke and common interface of
// rpcclient.Client and rpcclient.WSClient. Interface is anonymous
// according to assumption that common interface of these client types
// is not required by design and may diverge with changes.
var multiSchemeClient interface {
neoClient
invoker.RPCInvoke
// Init turns client to "ready-to-work" state.
Init() error
// GetContractStateByID returns state of the NNS contract on 1 input.
GetContractStateByID(int32) (*state.Contract, error)
}
var err error
uri, err := url.Parse(address)
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 {
return fmt.Errorf("create Neo WebSocket client: %w", err)
}
multiSchemeClient = (*neoWebSocket)(cWebSocket)
} else {
cHTTP, err := rpcclient.New(context.Background(), address, rpcclient.Options{})
multiSchemeClient, err = rpcclient.New(context.Background(), address, rpcclient.Options{})
if err != nil {
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 {
@ -102,7 +68,7 @@ func (n *NNS) Dial(address string) error {
return fmt.Errorf("get NNS contract state: %w", err)
}
n.neoClient = multiSchemeClient
n.invoker = invoker.New(multiSchemeClient, nil)
n.nnsContract = nnsContract.Hash
return nil
@ -116,36 +82,11 @@ func (n *NNS) Dial(address string) error {
//
// 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.call(n.nnsContract, "resolve", []smartcontract.Parameter{
{
Type: smartcontract.StringType,
Value: name + ".container",
},
{
Type: smartcontract.IntegerType,
Value: big.NewInt(int64(nns.TXT)),
},
})
arr, err := unwrap.Array(n.invoker.Call(n.nnsContract, "resolve",
name+".container", int64(nns.TXT),
))
if err != nil {
return cid.ID{}, fmt.Errorf("invoke NNS contract: %w", err)
}
if res.State != vmstate.Halt.String() {
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
if err != nil {
return cid.ID{}, fmt.Errorf("convert stack item to %s", stackitem.ArrayT)
}
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")
return cid.ID{}, fmt.Errorf("contract invocation: %w", err)
}
var id cid.ID
@ -161,7 +102,6 @@ func (n *NNS) ResolveContainerName(name string) (cid.ID, error) {
return id, nil
}
}
}
return cid.ID{}, errNotFound
}

View file

@ -9,11 +9,9 @@ import (
"testing"
"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/vm/stackitem"
"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"
"github.com/stretchr/testify/require"
)
@ -30,18 +28,16 @@ type testNeoClient struct {
err error
}
func (x *testNeoClient) call(contract util.Uint160, method string, prm []smartcontract.Parameter) (*result.Invoke, error) {
require.Equal(x.t, x.expectedContract, contract)
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)
func (x *testNeoClient) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
var domain string
val, ok := prm[0].Value.(string)
require.True(x.t, ok)
require.True(x.t, strings.HasSuffix(val, ".container"))
require.NotEmpty(x.t, strings.TrimSuffix(val, ".container"))
require.Equal(x.t, x.expectedContract, contract)
require.Equal(x.t, "resolve", operation)
require.Len(x.t, params, 2)
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
}
@ -78,14 +74,15 @@ func TestNNS_ResolveContainerName(t *testing.T) {
n := NNS{
nnsContract: nnsContract,
neoClient: testC,
invoker: testC,
}
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)
require.Error(t, err)
_, err2 := n.ResolveContainerName(testContainerName)
require.ErrorIs(t, err2, err1)
})
testC.err = nil