diff --git a/ns/nns.go b/ns/nns.go index 02daa6aa..fe47bb78 100644 --- a/ns/nns.go +++ b/ns/nns.go @@ -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,50 +82,24 @@ 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) + return cid.ID{}, fmt.Errorf("contract invocation: %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") - } + var id cid.ID - 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") + for i := range arr { + bs, err := arr[i].TryBytes() + if err != nil { + return cid.ID{}, fmt.Errorf("convert array item to byte slice: %w", err) } - var id cid.ID - - for i := range arr { - 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 - } + err = id.DecodeString(string(bs)) + if err == nil { + return id, nil } } diff --git a/ns/nns_test.go b/ns/nns_test.go index c541093e..53947d47 100644 --- a/ns/nns_test.go +++ b/ns/nns_test.go @@ -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