[#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

108
ns/nns.go
View file

@ -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
}
} }
} }

View file

@ -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