Merge pull request #2166 from nspcc-dev/fix-nns-compat
Fix NNS compatibility
This commit is contained in:
commit
63e00ac128
8 changed files with 132 additions and 17 deletions
|
@ -66,6 +66,13 @@ const (
|
||||||
millisecondsInYear = 365 * 24 * 3600 * 1000
|
millisecondsInYear = 365 * 24 * 3600 * 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RecordState is a type that registered entities are saved to.
|
||||||
|
type RecordState struct {
|
||||||
|
Name string
|
||||||
|
Type RecordType
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
// Update updates NameService contract.
|
// Update updates NameService contract.
|
||||||
func Update(nef []byte, manifest string) {
|
func Update(nef []byte, manifest string) {
|
||||||
checkCommittee()
|
checkCommittee()
|
||||||
|
@ -353,6 +360,15 @@ func Resolve(name string, typ RecordType) string {
|
||||||
return resolve(ctx, name, typ, 2)
|
return resolve(ctx, name, typ, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllRecords returns an Iterator with RecordState items for given name.
|
||||||
|
func GetAllRecords(name string) iterator.Iterator {
|
||||||
|
tokenID := []byte(tokenIDFromName(name))
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
_ = getNameState(ctx, tokenID) // ensure not expired
|
||||||
|
recordsKey := getRecordsKey(tokenID, name)
|
||||||
|
return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues)
|
||||||
|
}
|
||||||
|
|
||||||
// updateBalance updates account's balance and account's tokens.
|
// updateBalance updates account's balance and account's tokens.
|
||||||
func updateBalance(ctx storage.Context, tokenId []byte, acc interop.Hash160, diff int) {
|
func updateBalance(ctx storage.Context, tokenId []byte, acc interop.Hash160, diff int) {
|
||||||
balanceKey := append([]byte{prefixBalance}, acc...)
|
balanceKey := append([]byte{prefixBalance}, acc...)
|
||||||
|
@ -437,20 +453,35 @@ func putNameStateWithKey(ctx storage.Context, tokenKey []byte, ns NameState) {
|
||||||
// getRecord returns domain record.
|
// getRecord returns domain record.
|
||||||
func getRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType) string {
|
func getRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType) string {
|
||||||
recordKey := getRecordKey(tokenId, name, typ)
|
recordKey := getRecordKey(tokenId, name, typ)
|
||||||
record := storage.Get(ctx, recordKey)
|
recBytes := storage.Get(ctx, recordKey)
|
||||||
return record.(string)
|
if recBytes == nil {
|
||||||
|
return recBytes.(string) // A hack to actually return NULL.
|
||||||
|
}
|
||||||
|
record := std.Deserialize(recBytes.([]byte)).(RecordState)
|
||||||
|
return record.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
// putRecord stores domain record.
|
// putRecord stores domain record.
|
||||||
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, record string) {
|
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, record string) {
|
||||||
recordKey := getRecordKey(tokenId, name, typ)
|
recordKey := getRecordKey(tokenId, name, typ)
|
||||||
storage.Put(ctx, recordKey, record)
|
rs := RecordState{
|
||||||
|
Name: name,
|
||||||
|
Type: typ,
|
||||||
|
Data: record,
|
||||||
|
}
|
||||||
|
recBytes := std.Serialize(rs)
|
||||||
|
storage.Put(ctx, recordKey, recBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRecordsKey returns prefix used to store domain records of different types.
|
||||||
|
func getRecordsKey(tokenId []byte, name string) []byte {
|
||||||
|
recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...)
|
||||||
|
return append(recordKey, getTokenKey([]byte(name))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRecordKey returns key used to store domain records.
|
// getRecordKey returns key used to store domain records.
|
||||||
func getRecordKey(tokenId []byte, name string, typ RecordType) []byte {
|
func getRecordKey(tokenId []byte, name string, typ RecordType) []byte {
|
||||||
recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...)
|
recordKey := getRecordsKey(tokenId, name)
|
||||||
recordKey = append(recordKey, getTokenKey([]byte(name))...)
|
|
||||||
return append(recordKey, []byte{byte(typ)}...)
|
return append(recordKey, []byte{byte(typ)}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,8 +617,14 @@ func checkIPv6(data string) bool {
|
||||||
for i, f := range fragments {
|
for i, f := range fragments {
|
||||||
if len(f) == 0 {
|
if len(f) == 0 {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
|
if len(fragments[1]) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
nums[i] = 0
|
nums[i] = 0
|
||||||
} else if i == l-1 {
|
} else if i == l-1 {
|
||||||
|
if len(fragments[i-1]) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
nums[7] = 0
|
nums[7] = 0
|
||||||
} else if hasEmpty {
|
} else if hasEmpty {
|
||||||
return false
|
return false
|
||||||
|
@ -613,6 +650,9 @@ func checkIPv6(data string) bool {
|
||||||
nums[idx] = n
|
nums[idx] = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if l < 8 && !hasEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
f0 := nums[0]
|
f0 := nums[0]
|
||||||
if f0 < 0x2000 || f0 == 0x2002 || f0 == 0x3ffe || f0 > 0x3fff { // IPv6 Global Unicast https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
|
if f0 < 0x2000 || f0 == 0x2002 || f0 == 0x3ffe || f0 > 0x3fff { // IPv6 Global Unicast https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
|
||||||
|
@ -646,10 +686,12 @@ func resolve(ctx storage.Context, name string, typ RecordType, redirect int) str
|
||||||
records := getRecords(ctx, name)
|
records := getRecords(ctx, name)
|
||||||
cname := ""
|
cname := ""
|
||||||
for iterator.Next(records) {
|
for iterator.Next(records) {
|
||||||
r := iterator.Value(records).([]string)
|
r := iterator.Value(records).(struct {
|
||||||
key := []byte(r[0])
|
key string
|
||||||
value := r[1]
|
rs RecordState
|
||||||
rTyp := key[len(key)-1]
|
})
|
||||||
|
value := r.rs.Data
|
||||||
|
rTyp := r.key[len(r.key)-1]
|
||||||
if rTyp == byte(typ) {
|
if rTyp == byte(typ) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@ -668,7 +710,6 @@ func resolve(ctx storage.Context, name string, typ RecordType, redirect int) str
|
||||||
func getRecords(ctx storage.Context, name string) iterator.Iterator {
|
func getRecords(ctx storage.Context, name string) iterator.Iterator {
|
||||||
tokenID := []byte(tokenIDFromName(name))
|
tokenID := []byte(tokenIDFromName(name))
|
||||||
_ = getNameState(ctx, tokenID)
|
_ = getNameState(ctx, tokenID)
|
||||||
recordsKey := append([]byte{prefixRecord}, getTokenKey(tokenID)...)
|
recordsKey := getRecordsKey(tokenID, name)
|
||||||
recordsKey = append(recordsKey, getTokenKey([]byte(name))...)
|
return storage.Find(ctx, recordsKey, storage.DeserializeValues)
|
||||||
return storage.Find(ctx, recordsKey, storage.None)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
name: "NameService"
|
name: "NameService"
|
||||||
supportedstandards: ["NEP-11"]
|
supportedstandards: ["NEP-11"]
|
||||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
|
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
|
||||||
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord", "resolve"]
|
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord",
|
||||||
|
"resolve", "getAllRecords"]
|
||||||
events:
|
events:
|
||||||
- name: Transfer
|
- name: Transfer
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
@ -441,7 +441,7 @@ func TestResolve(t *testing.T) {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultNameServiceDomainPrice = 10_0000_0000
|
defaultNameServiceDomainPrice = 10_0000_0000
|
||||||
defaultNameServiceSysfee = 4000_0000
|
defaultNameServiceSysfee = 6000_0000
|
||||||
defaultRegisterSysfee = 10_0000_0000 + defaultNameServiceDomainPrice
|
defaultRegisterSysfee = 10_0000_0000 + defaultNameServiceDomainPrice
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
nns "github.com/nspcc-dev/neo-go/examples/nft-nd-nns"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -124,6 +125,35 @@ func topIterableFromStack(st []stackitem.Item, resultItemType interface{}) ([]in
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode uint160 from stackitem #%d: %w", i, err)
|
return nil, fmt.Errorf("failed to decode uint160 from stackitem #%d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
case nns.RecordState:
|
||||||
|
rs, ok := iter.Values[i].Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: not a struct", i)
|
||||||
|
}
|
||||||
|
if len(rs) != 3 {
|
||||||
|
return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: wrong number of elements", i)
|
||||||
|
}
|
||||||
|
name, err := rs[0].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deocde RecordState from stackitem #%d: %w", i, err)
|
||||||
|
}
|
||||||
|
typ, err := rs[1].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deocde RecordState from stackitem #%d: %w", i, err)
|
||||||
|
}
|
||||||
|
data, err := rs[2].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deocde RecordState from stackitem #%d: %w", i, err)
|
||||||
|
}
|
||||||
|
u64Typ := typ.Uint64()
|
||||||
|
if !typ.IsUint64() || u64Typ > 255 {
|
||||||
|
return nil, fmt.Errorf("failed to deocde RecordState from stackitem #%d: bad type", i)
|
||||||
|
}
|
||||||
|
result[i] = nns.RecordState{
|
||||||
|
Name: string(name),
|
||||||
|
Type: nns.RecordType(u64Typ),
|
||||||
|
Data: string(data),
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unsupported iterable type")
|
return nil, errors.New("unsupported iterable type")
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,3 +114,30 @@ func (c *Client) NNSIsAvailable(nnsHash util.Uint160, name string) (bool, error)
|
||||||
}
|
}
|
||||||
return topBoolFromStack(result.Stack)
|
return topBoolFromStack(result.Stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NNSGetAllRecords returns all records for a given name from NNS service.
|
||||||
|
func (c *Client) NNSGetAllRecords(nnsHash util.Uint160, name string) ([]nns.RecordState, error) {
|
||||||
|
result, err := c.InvokeFunction(nnsHash, "getAllRecords", []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: name,
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = getInvocationError(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr, err := topIterableFromStack(result.Stack, nns.RecordState{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
|
||||||
|
}
|
||||||
|
rss := make([]nns.RecordState, len(arr))
|
||||||
|
for i := range rss {
|
||||||
|
rss[i] = arr[i].(nns.RecordState)
|
||||||
|
}
|
||||||
|
return rss, nil
|
||||||
|
}
|
||||||
|
|
|
@ -876,4 +876,19 @@ func TestClient_NNS(t *testing.T) {
|
||||||
_, err := c.NNSResolve(nsHash, "neogo.com", nns.CNAME)
|
_, err := c.NNSResolve(nsHash, "neogo.com", nns.CNAME)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
t.Run("NNSGetAllRecords, good", func(t *testing.T) {
|
||||||
|
rss, err := c.NNSGetAllRecords(nsHash, "neo.com")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []nns.RecordState{
|
||||||
|
nns.RecordState{
|
||||||
|
Name: "neo.com",
|
||||||
|
Type: nns.A,
|
||||||
|
Data: "1.2.3.4",
|
||||||
|
},
|
||||||
|
}, rss)
|
||||||
|
})
|
||||||
|
t.Run("NNSGetAllRecords, bad", func(t *testing.T) {
|
||||||
|
_, err := c.NNSGetAllRecords(nsHash, "neopython.com")
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,14 +55,15 @@ type rpcTestCase struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const testContractHash = "bb6a679438ce0fc6cb0ed1aa85ce83cf96cd3aeb"
|
const testContractHash = "bb6a679438ce0fc6cb0ed1aa85ce83cf96cd3aeb"
|
||||||
const deploymentTxHash = "1f8792e07f223e5e83f86cda3327cbe78c15ea382a1c350101c9119747682ce2"
|
const deploymentTxHash = "4c631654b04f6a3b25af45082d260b555de4d0eeba6b7697e3a0f18b3f96434f"
|
||||||
const genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4"
|
const genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4"
|
||||||
|
|
||||||
const verifyContractHash = "f68822e4ecd93de334bdf1f7c409eda3431bcbd0"
|
const verifyContractHash = "f68822e4ecd93de334bdf1f7c409eda3431bcbd0"
|
||||||
const verifyContractAVM = "VwIAQS1RCDAhcAwU7p6iLCfjS9AUj8QQjgj3To9QSLLbMHFoE87bKGnbKJdA"
|
const verifyContractAVM = "VwIAQS1RCDAhcAwU7p6iLCfjS9AUj8QQjgj3To9QSLLbMHFoE87bKGnbKJdA"
|
||||||
const verifyWithArgsContractHash = "947c780f45b2a3d32e946355ee5cb57faf4decb7"
|
const verifyWithArgsContractHash = "947c780f45b2a3d32e946355ee5cb57faf4decb7"
|
||||||
const invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGhB+CfsjCGqJgQRQAwUDQ8DAgkAAgEDBwMEBQIBAA4GDAnbMHFpQfgn7IwhqiYEEkATQA=="
|
const invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGhB+CfsjCGqJgQRQAwUDQ8DAgkAAgEDBwMEBQIBAA4GDAnbMHFpQfgn7IwhqiYEEkATQA=="
|
||||||
const nameServiceContractHash = "66206eb850818ec862a9332e0da10b9b7826cb0b"
|
|
||||||
|
const nameServiceContractHash = "3a602b3e7cfd760850bfac44f4a9bb0ebad3e2dc"
|
||||||
|
|
||||||
var rpcTestCases = map[string][]rpcTestCase{
|
var rpcTestCases = map[string][]rpcTestCase{
|
||||||
"getapplicationlog": {
|
"getapplicationlog": {
|
||||||
|
@ -1638,7 +1639,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Asset: e.chain.UtilityTokenHash(),
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
Amount: "57941227260",
|
Amount: "57900879260",
|
||||||
LastUpdated: 15,
|
LastUpdated: 15,
|
||||||
}},
|
}},
|
||||||
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
||||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
Loading…
Reference in a new issue