[#42] add ResolveContractHash method #111

Merged
alexvanin merged 1 commit from pogpp/frostfs-sdk-go:feature/resolveContractHash into master 2023-07-13 12:40:49 +00:00
2 changed files with 127 additions and 0 deletions

View file

@ -10,6 +10,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"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/encoding/address"
"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/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
@ -116,3 +117,46 @@ func (n *NNS) ResolveContainerDomain(domain container.Domain) (cid.ID, error) {
return cid.ID{}, errNotFound return cid.ID{}, errNotFound
} }
// ResolveContractHash looks up for NNS TXT records for the given container domain
// by calling `resolve` method of NNS contract. Returns the first record which represents
// valid contract hash 20 bytes long unsigned integer. Otherwise, returns an error.
dkirillov marked this conversation as resolved Outdated

It doesn't return first record which represents valid container ID in a string format

It doesn't return `first record which represents valid container ID in a string format`
//
// ResolveContractHash MUST NOT be called before successful Dial.
//
// See also https://docs.neo.org/docs/en-us/reference/nns.html.
func (n *NNS) ResolveContractHash(domain container.Domain) (util.Uint160, error) {
dkirillov marked this conversation as resolved Outdated

I'm not sure if it's a good idea to use container.Domain when we resolve contract hash. Maybe it's too confusing and we should provide just string as argument. What do you think?

I'm not sure if it's a good idea to use `container.Domain` when we resolve contract hash. Maybe it's too confusing and we should provide just string as argument. What do you think?

String should be a valid domain, e.g. container.frostfs. I would rather move container.Domain structure from container package to some sort of shared package.

String should be a valid domain, e.g. `container.frostfs`. I would rather move `container.Domain` structure from container package to some sort of shared package.
item, err := unwrap.Item(n.invoker.Call(n.nnsContract, "resolve",
domain.Name()+"."+domain.Zone(), int64(nns.TXT),
))
if err != nil {
return util.Uint160{}, fmt.Errorf("contract invocation: %w", err)
}
if _, ok := item.(stackitem.Null); !ok {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
// unexpected for types from stackitem package
return util.Uint160{}, errors.New("invalid cast to stack item slice")
}
for i := range arr {
recordValue, err := arr[i].TryBytes()
if err != nil {
return util.Uint160{}, fmt.Errorf("convert array item to byte slice: %w", err)
}
strRecordValue := string(recordValue)
scriptHash, err := address.StringToUint160(strRecordValue)

Can it be false? We check the error 5 lines above.

Can it be false? We check the error 5 lines above.

You've already checked that err != nil above :)

You've already checked that `err != nil` above :)
if err == nil {
return scriptHash, nil
}
scriptHash, err = util.Uint160DecodeStringLE(strRecordValue)
if err == nil {
return scriptHash, nil
}
}
}
return util.Uint160{}, errNotFound
}

View file

@ -154,3 +154,86 @@ func TestNNS_ResolveContainerDomain(t *testing.T) {
require.Equal(t, id, res) require.Equal(t, id, res)
}) })
} }
func TestNNS_ResolveContractHash(t *testing.T) {
var testContainerDomain container.Domain
testContainerDomain.SetName("some_container")
var nnsContract util.Uint160
rand.Read(nnsContract[:])
testC := &testNeoClient{
t: t,
expectedContract: nnsContract,
}
n := NNS{
nnsContract: nnsContract,
invoker: testC,
}
t.Run("invocation failure", func(t *testing.T) {
err1 := errors.New("invoke err")
testC.err = err1
_, err2 := n.ResolveContractHash(testContainerDomain)
require.ErrorIs(t, err2, err1)
})
testC.err = nil
t.Run("fault exception", func(t *testing.T) {
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
testC.res.State = vmstate.Halt.String()
t.Run("empty stack", func(t *testing.T) {
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
testC.res.Stack = make([]stackitem.Item, 1)
t.Run("non-array last stack item", func(t *testing.T) {
testC.res.Stack[0] = stackitem.NewBigInteger(big.NewInt(11))
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
t.Run("null array", func(t *testing.T) {
testC.res.Stack[0] = stackitem.Null{}
_, err := n.ResolveContractHash(testContainerDomain)
require.ErrorIs(t, err, errNotFound)
})
t.Run("array stack item with non-slice value", func(t *testing.T) {
testC.res.Stack[0] = brokenArrayStackItem{}
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
arr := make([]stackitem.Item, 2)
testC.res.Stack[0] = stackitem.NewArray(arr)
t.Run("non-bytes array element", func(t *testing.T) {
arr[0] = stackitem.NewArray(nil)
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
arr[0] = stackitem.NewByteArray([]byte("some byte array 1"))
t.Run("non-container array elements", func(t *testing.T) {
arr[1] = stackitem.NewByteArray([]byte("some byte array 2"))
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
}