diff --git a/examples/nft-nd-nns/nns.go b/examples/nft-nd-nns/nns.go index fb21e1c1d..0206b9524 100644 --- a/examples/nft-nd-nns/nns.go +++ b/examples/nft-nd-nns/nns.go @@ -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) } diff --git a/examples/nft-nd-nns/nns.yml b/examples/nft-nd-nns/nns.yml index 01e89b72b..289793c9d 100644 --- a/examples/nft-nd-nns/nns.yml +++ b/examples/nft-nd-nns/nns.yml @@ -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: diff --git a/pkg/core/nonnative_name_service_test.go b/pkg/core/nonnative_name_service_test.go index 43486b2fb..0a071001c 100644 --- a/pkg/core/nonnative_name_service_test.go +++ b/pkg/core/nonnative_name_service_test.go @@ -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 ) diff --git a/pkg/rpc/client/helper.go b/pkg/rpc/client/helper.go index 2744deb16..0a7b0c0fa 100644 --- a/pkg/rpc/client/helper.go +++ b/pkg/rpc/client/helper.go @@ -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") } diff --git a/pkg/rpc/client/native.go b/pkg/rpc/client/native.go index bfbed495e..ab36030c0 100644 --- a/pkg/rpc/client/native.go +++ b/pkg/rpc/client/native.go @@ -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 +} diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index 606750ef0..2b9b2210c 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -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) + }) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 66260c311..2a0c307c5 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -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(), diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index cc610cb70..155622bac 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ