2021-08-31 12:06:17 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
2021-10-12 10:33:57 +00:00
|
|
|
"errors"
|
2021-08-31 12:06:17 +00:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
nns "github.com/nspcc-dev/neo-go/examples/nft-nd-nns"
|
2022-01-29 11:15:05 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2021-10-12 10:33:57 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
2021-08-31 12:06:17 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2021-10-12 10:33:57 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
2021-08-31 12:06:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
nnsContractID = 1 // NNS contract must be deployed first in side chain
|
|
|
|
|
|
|
|
// NNSAuditContractName is a name of the audit contract in NNS.
|
|
|
|
NNSAuditContractName = "audit.neofs"
|
|
|
|
// NNSBalanceContractName is a name of the balance contract in NNS.
|
|
|
|
NNSBalanceContractName = "balance.neofs"
|
|
|
|
// NNSContainerContractName is a name of the container contract in NNS.
|
|
|
|
NNSContainerContractName = "container.neofs"
|
|
|
|
// NNSNeoFSIDContractName is a name of the neofsid contract in NNS.
|
|
|
|
NNSNeoFSIDContractName = "neofsid.neofs"
|
|
|
|
// NNSNetmapContractName is a name of the netmap contract in NNS.
|
|
|
|
NNSNetmapContractName = "netmap.neofs"
|
|
|
|
// NNSProxyContractName is a name of the proxy contract in NNS.
|
|
|
|
NNSProxyContractName = "proxy.neofs"
|
|
|
|
// NNSReputationContractName is a name of the reputation contract in NNS.
|
|
|
|
NNSReputationContractName = "reputation.neofs"
|
2021-11-26 14:15:22 +00:00
|
|
|
// NNSSubnetworkContractName is a name of the subnet contract in NNS.
|
|
|
|
NNSSubnetworkContractName = "subnet.neofs"
|
2022-01-29 11:15:05 +00:00
|
|
|
// NNSGroupKeyName is a name for the NeoFS group key record in NNS.
|
|
|
|
NNSGroupKeyName = "group.neofs"
|
2021-08-31 12:06:17 +00:00
|
|
|
)
|
|
|
|
|
2021-10-20 13:17:01 +00:00
|
|
|
var (
|
|
|
|
// ErrNNSRecordNotFound means that there is no such record in NNS contract.
|
|
|
|
ErrNNSRecordNotFound = errors.New("record has not been found in NNS contract")
|
|
|
|
|
|
|
|
errEmptyResultStack = errors.New("returned result stack is empty")
|
|
|
|
)
|
|
|
|
|
2021-08-31 12:06:17 +00:00
|
|
|
// NNSAlphabetContractName returns contract name of the alphabet contract in NNS
|
|
|
|
// based on alphabet index.
|
|
|
|
func NNSAlphabetContractName(index int) string {
|
|
|
|
return "alphabet" + strconv.Itoa(index) + ".neofs"
|
|
|
|
}
|
|
|
|
|
|
|
|
// NNSContractAddress returns contract address script hash based on its name
|
|
|
|
// in NNS contract.
|
2021-10-20 13:17:01 +00:00
|
|
|
// If script hash has not been found, returns ErrNNSRecordNotFound.
|
2021-08-31 12:06:17 +00:00
|
|
|
func (c *Client) NNSContractAddress(name string) (sh util.Uint160, err error) {
|
|
|
|
if c.multiClient != nil {
|
|
|
|
return sh, c.multiClient.iterateClients(func(c *Client) error {
|
|
|
|
sh, err = c.NNSContractAddress(name)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-01-29 11:15:05 +00:00
|
|
|
nnsHash, err := c.NNSHash()
|
|
|
|
if err != nil {
|
|
|
|
return util.Uint160{}, err
|
2021-08-31 12:06:17 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 11:15:05 +00:00
|
|
|
sh, err = nnsResolve(c.client, nnsHash, name)
|
2021-08-31 12:06:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return sh, fmt.Errorf("NNS.resolve: %w", err)
|
|
|
|
}
|
2021-10-12 10:33:57 +00:00
|
|
|
return sh, nil
|
|
|
|
}
|
2021-08-31 12:06:17 +00:00
|
|
|
|
2022-01-29 11:15:05 +00:00
|
|
|
// NNSHash returns NNS contract hash.
|
|
|
|
func (c *Client) NNSHash() (util.Uint160, error) {
|
|
|
|
if c.multiClient != nil {
|
|
|
|
var sh util.Uint160
|
|
|
|
return sh, c.multiClient.iterateClients(func(c *Client) error {
|
|
|
|
var err error
|
|
|
|
sh, err = c.NNSHash()
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.nnsHash.Equals(util.Uint160{}) {
|
|
|
|
cs, err := c.client.GetContractStateByID(nnsContractID)
|
|
|
|
if err != nil {
|
|
|
|
return util.Uint160{}, fmt.Errorf("NNS contract state: %w", err)
|
|
|
|
}
|
|
|
|
c.nnsHash = cs.Hash
|
|
|
|
}
|
|
|
|
return c.nnsHash, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func nnsResolveItem(c *client.Client, nnsHash util.Uint160, domain string) (stackitem.Item, error) {
|
2021-10-20 13:17:01 +00:00
|
|
|
found, err := exists(c, nnsHash, domain)
|
|
|
|
if err != nil {
|
2022-01-29 11:15:05 +00:00
|
|
|
return nil, fmt.Errorf("could not check presence in NNS contract for %s: %w", domain, err)
|
2021-10-20 13:17:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
2022-01-29 11:15:05 +00:00
|
|
|
return nil, ErrNNSRecordNotFound
|
2021-10-20 13:17:01 +00:00
|
|
|
}
|
|
|
|
|
2021-10-12 10:33:57 +00:00
|
|
|
result, err := c.InvokeFunction(nnsHash, "resolve", []smartcontract.Parameter{
|
|
|
|
{
|
|
|
|
Type: smartcontract.StringType,
|
|
|
|
Value: domain,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: smartcontract.IntegerType,
|
|
|
|
Value: int64(nns.TXT),
|
|
|
|
},
|
|
|
|
}, nil)
|
2021-08-31 12:06:17 +00:00
|
|
|
if err != nil {
|
2022-01-29 11:15:05 +00:00
|
|
|
return nil, err
|
2021-10-12 10:33:57 +00:00
|
|
|
}
|
|
|
|
if result.State != vm.HaltState.String() {
|
2022-01-29 11:15:05 +00:00
|
|
|
return nil, fmt.Errorf("invocation failed: %s", result.FaultException)
|
2021-10-12 10:33:57 +00:00
|
|
|
}
|
|
|
|
if len(result.Stack) == 0 {
|
2022-01-29 11:15:05 +00:00
|
|
|
return nil, errEmptyResultStack
|
|
|
|
}
|
|
|
|
return result.Stack[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func nnsResolve(c *client.Client, nnsHash util.Uint160, domain string) (util.Uint160, error) {
|
|
|
|
res, err := nnsResolveItem(c, nnsHash, domain)
|
|
|
|
if err != nil {
|
|
|
|
return util.Uint160{}, err
|
2021-08-31 12:06:17 +00:00
|
|
|
}
|
|
|
|
|
2021-10-12 10:33:57 +00:00
|
|
|
// Parse the result of resolving NNS record.
|
|
|
|
// It works with multiple formats (corresponding to multiple NNS versions).
|
|
|
|
// If array of hashes is provided, it returns only the first one.
|
|
|
|
if arr, ok := res.Value().([]stackitem.Item); ok {
|
|
|
|
if len(arr) == 0 {
|
|
|
|
return util.Uint160{}, errors.New("NNS record is missing")
|
|
|
|
}
|
|
|
|
res = arr[0]
|
|
|
|
}
|
|
|
|
bs, err := res.TryBytes()
|
|
|
|
if err != nil {
|
|
|
|
return util.Uint160{}, fmt.Errorf("malformed response: %w", err)
|
|
|
|
}
|
|
|
|
return util.Uint160DecodeStringLE(string(bs))
|
2021-08-31 12:06:17 +00:00
|
|
|
}
|
2021-10-20 13:17:01 +00:00
|
|
|
|
|
|
|
func exists(c *client.Client, nnsHash util.Uint160, domain string) (bool, error) {
|
|
|
|
result, err := c.InvokeFunction(nnsHash, "isAvailable", []smartcontract.Parameter{
|
|
|
|
{
|
|
|
|
Type: smartcontract.StringType,
|
|
|
|
Value: domain,
|
|
|
|
},
|
|
|
|
}, nil)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(result.Stack) == 0 {
|
|
|
|
return false, errEmptyResultStack
|
|
|
|
}
|
|
|
|
|
|
|
|
res := result.Stack[0]
|
|
|
|
|
|
|
|
available, err := res.TryBool()
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("malformed response: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// not available means that it is taken
|
|
|
|
// and, therefore, exists
|
|
|
|
return !available, nil
|
|
|
|
}
|
2022-01-29 11:15:05 +00:00
|
|
|
|
|
|
|
// ContractGroupKey returns public key designating NeoFS contract group.
|
|
|
|
func (c *Client) ContractGroupKey() (*keys.PublicKey, error) {
|
|
|
|
if c.groupKey != nil {
|
|
|
|
return c.groupKey, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
nnsHash, err := c.NNSHash()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
item, err := nnsResolveItem(c.client, nnsHash, NNSGroupKeyName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
bs, err := item.TryBytes()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pub, err := keys.NewPublicKeyFromString(string(bs))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c.groupKey = pub
|
|
|
|
return pub, nil
|
|
|
|
}
|