Merge pull request #2166 from nspcc-dev/fix-nns-compat

Fix NNS compatibility
This commit is contained in:
Roman Khimov 2021-09-10 18:10:17 +03:00 committed by GitHub
commit 63e00ac128
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 132 additions and 17 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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(),

Binary file not shown.