forked from TrueCloudLab/neoneo-go
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
|
||||
)
|
||||
|
||||
// RecordState is a type that registered entities are saved to.
|
||||
type RecordState struct {
|
||||
Name string
|
||||
Type RecordType
|
||||
Data string
|
||||
}
|
||||
|
||||
// Update updates NameService contract.
|
||||
func Update(nef []byte, manifest string) {
|
||||
checkCommittee()
|
||||
|
@ -353,6 +360,15 @@ func Resolve(name string, typ RecordType) string {
|
|||
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.
|
||||
func updateBalance(ctx storage.Context, tokenId []byte, acc interop.Hash160, diff int) {
|
||||
balanceKey := append([]byte{prefixBalance}, acc...)
|
||||
|
@ -437,20 +453,35 @@ func putNameStateWithKey(ctx storage.Context, tokenKey []byte, ns NameState) {
|
|||
// getRecord returns domain record.
|
||||
func getRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType) string {
|
||||
recordKey := getRecordKey(tokenId, name, typ)
|
||||
record := storage.Get(ctx, recordKey)
|
||||
return record.(string)
|
||||
recBytes := storage.Get(ctx, recordKey)
|
||||
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.
|
||||
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, record string) {
|
||||
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.
|
||||
func getRecordKey(tokenId []byte, name string, typ RecordType) []byte {
|
||||
recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...)
|
||||
recordKey = append(recordKey, getTokenKey([]byte(name))...)
|
||||
recordKey := getRecordsKey(tokenId, name)
|
||||
return append(recordKey, []byte{byte(typ)}...)
|
||||
}
|
||||
|
||||
|
@ -586,8 +617,14 @@ func checkIPv6(data string) bool {
|
|||
for i, f := range fragments {
|
||||
if len(f) == 0 {
|
||||
if i == 0 {
|
||||
if len(fragments[1]) != 0 {
|
||||
return false
|
||||
}
|
||||
nums[i] = 0
|
||||
} else if i == l-1 {
|
||||
if len(fragments[i-1]) != 0 {
|
||||
return false
|
||||
}
|
||||
nums[7] = 0
|
||||
} else if hasEmpty {
|
||||
return false
|
||||
|
@ -613,6 +650,9 @@ func checkIPv6(data string) bool {
|
|||
nums[idx] = n
|
||||
}
|
||||
}
|
||||
if l < 8 && !hasEmpty {
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -646,10 +686,12 @@ func resolve(ctx storage.Context, name string, typ RecordType, redirect int) str
|
|||
records := getRecords(ctx, name)
|
||||
cname := ""
|
||||
for iterator.Next(records) {
|
||||
r := iterator.Value(records).([]string)
|
||||
key := []byte(r[0])
|
||||
value := r[1]
|
||||
rTyp := key[len(key)-1]
|
||||
r := iterator.Value(records).(struct {
|
||||
key string
|
||||
rs RecordState
|
||||
})
|
||||
value := r.rs.Data
|
||||
rTyp := r.key[len(r.key)-1]
|
||||
if rTyp == byte(typ) {
|
||||
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 {
|
||||
tokenID := []byte(tokenIDFromName(name))
|
||||
_ = getNameState(ctx, tokenID)
|
||||
recordsKey := append([]byte{prefixRecord}, getTokenKey(tokenID)...)
|
||||
recordsKey = append(recordsKey, getTokenKey([]byte(name))...)
|
||||
return storage.Find(ctx, recordsKey, storage.None)
|
||||
recordsKey := getRecordsKey(tokenID, name)
|
||||
return storage.Find(ctx, recordsKey, storage.DeserializeValues)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
name: "NameService"
|
||||
supportedstandards: ["NEP-11"]
|
||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
|
||||
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord", "resolve"]
|
||||
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord",
|
||||
"resolve", "getAllRecords"]
|
||||
events:
|
||||
- name: Transfer
|
||||
parameters:
|
||||
|
|
|
@ -441,7 +441,7 @@ func TestResolve(t *testing.T) {
|
|||
|
||||
const (
|
||||
defaultNameServiceDomainPrice = 10_0000_0000
|
||||
defaultNameServiceSysfee = 4000_0000
|
||||
defaultNameServiceSysfee = 6000_0000
|
||||
defaultRegisterSysfee = 10_0000_0000 + defaultNameServiceDomainPrice
|
||||
)
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"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/rpc/response/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -124,6 +125,35 @@ func topIterableFromStack(st []stackitem.Item, resultItemType interface{}) ([]in
|
|||
if err != nil {
|
||||
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:
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
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 deploymentTxHash = "1f8792e07f223e5e83f86cda3327cbe78c15ea382a1c350101c9119747682ce2"
|
||||
const deploymentTxHash = "4c631654b04f6a3b25af45082d260b555de4d0eeba6b7697e3a0f18b3f96434f"
|
||||
const genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4"
|
||||
|
||||
const verifyContractHash = "f68822e4ecd93de334bdf1f7c409eda3431bcbd0"
|
||||
const verifyContractAVM = "VwIAQS1RCDAhcAwU7p6iLCfjS9AUj8QQjgj3To9QSLLbMHFoE87bKGnbKJdA"
|
||||
const verifyWithArgsContractHash = "947c780f45b2a3d32e946355ee5cb57faf4decb7"
|
||||
const invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGhB+CfsjCGqJgQRQAwUDQ8DAgkAAgEDBwMEBQIBAA4GDAnbMHFpQfgn7IwhqiYEEkATQA=="
|
||||
const nameServiceContractHash = "66206eb850818ec862a9332e0da10b9b7826cb0b"
|
||||
|
||||
const nameServiceContractHash = "3a602b3e7cfd760850bfac44f4a9bb0ebad3e2dc"
|
||||
|
||||
var rpcTestCases = map[string][]rpcTestCase{
|
||||
"getapplicationlog": {
|
||||
|
@ -1638,7 +1639,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
|
|||
},
|
||||
{
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Amount: "57941227260",
|
||||
Amount: "57900879260",
|
||||
LastUpdated: 15,
|
||||
}},
|
||||
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