nns: allow multiple records of the same type
Except for the CNAME records. Port6ea4573ef8
andf4762c1b56
.
This commit is contained in:
parent
c296f8804c
commit
c9050cef4b
4 changed files with 228 additions and 103 deletions
|
@ -55,6 +55,9 @@ const (
|
|||
maxDomainNameLength = 255
|
||||
// maxTXTRecordLength is the maximum length of the TXT domain record.
|
||||
maxTXTRecordLength = 255
|
||||
// maxRecordID is the maximum value of record ID (the upper bound for the number
|
||||
// of records with the same type).
|
||||
maxRecordID = 255
|
||||
)
|
||||
|
||||
// Other constants.
|
||||
|
@ -70,6 +73,7 @@ type RecordState struct {
|
|||
Name string
|
||||
Type RecordType
|
||||
Data string
|
||||
ID byte
|
||||
}
|
||||
|
||||
// Update updates NameService contract.
|
||||
|
@ -337,8 +341,39 @@ func SetAdmin(name string, admin interop.Hash160) {
|
|||
putNameState(ctx, ns)
|
||||
}
|
||||
|
||||
// SetRecord adds new record of the specified type to the provided domain.
|
||||
func SetRecord(name string, typ RecordType, data string) {
|
||||
// SetRecord updates record of the specified type and ID.
|
||||
func SetRecord(name string, typ RecordType, id byte, data string) {
|
||||
ctx := storage.GetContext()
|
||||
tokenID := checkRecord(ctx, name, typ, data)
|
||||
recordKey := getRecordKey(tokenID, name, typ, id)
|
||||
recBytes := storage.Get(ctx, recordKey)
|
||||
if recBytes == nil {
|
||||
panic("unknown record")
|
||||
}
|
||||
putRecord(ctx, tokenID, name, typ, id, data)
|
||||
}
|
||||
|
||||
// AddRecord adds new record of the specified type to the provided domain.
|
||||
func AddRecord(name string, typ RecordType, data string) {
|
||||
ctx := storage.GetContext()
|
||||
tokenID := checkRecord(ctx, name, typ, data)
|
||||
recordsPrefix := getRecordsByTypePrefix(tokenID, name, typ)
|
||||
var id byte
|
||||
records := storage.Find(ctx, recordsPrefix, storage.KeysOnly)
|
||||
for iterator.Next(records) {
|
||||
id++
|
||||
}
|
||||
if id > maxRecordID {
|
||||
panic("maximum number of records reached")
|
||||
}
|
||||
if typ == CNAME && id != 0 {
|
||||
panic("multiple CNAME records")
|
||||
}
|
||||
putRecord(ctx, tokenID, name, typ, id, data)
|
||||
}
|
||||
|
||||
// checkRecord performs record validness check and returns token ID.
|
||||
func checkRecord(ctx storage.Context, name string, typ RecordType, data string) []byte {
|
||||
tokenID := []byte(tokenIDFromName(name))
|
||||
var ok bool
|
||||
switch typ {
|
||||
|
@ -356,44 +391,46 @@ func SetRecord(name string, typ RecordType, data string) {
|
|||
if !ok {
|
||||
panic("invalid record data")
|
||||
}
|
||||
ctx := storage.GetContext()
|
||||
ns := getNameState(ctx, tokenID)
|
||||
ns.checkAdmin()
|
||||
putRecord(ctx, tokenID, name, typ, data)
|
||||
return tokenID
|
||||
}
|
||||
|
||||
// GetRecord returns domain record of the specified type if it exists or an empty
|
||||
// string if not.
|
||||
func GetRecord(name string, typ RecordType) string {
|
||||
// GetRecords returns domain records of the specified type if they exist or an empty
|
||||
// array if not.
|
||||
func GetRecords(name string, typ RecordType) []string {
|
||||
tokenID := []byte(tokenIDFromName(name))
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
_ = getNameState(ctx, tokenID) // ensure not expired
|
||||
return getRecord(ctx, tokenID, name, typ)
|
||||
return getRecordsByType(ctx, tokenID, name, typ)
|
||||
}
|
||||
|
||||
// DeleteRecord removes domain record with the specified type.
|
||||
func DeleteRecord(name string, typ RecordType) {
|
||||
// DeleteRecords removes all domain records with the specified type.
|
||||
func DeleteRecords(name string, typ RecordType) {
|
||||
tokenID := []byte(tokenIDFromName(name))
|
||||
ctx := storage.GetContext()
|
||||
ns := getNameState(ctx, tokenID)
|
||||
ns.checkAdmin()
|
||||
recordKey := getRecordKey(tokenID, name, typ)
|
||||
storage.Delete(ctx, recordKey)
|
||||
recordsPrefix := getRecordsByTypePrefix(tokenID, name, typ)
|
||||
records := storage.Find(ctx, recordsPrefix, storage.KeysOnly)
|
||||
for iterator.Next(records) {
|
||||
key := iterator.Value(records).(string)
|
||||
storage.Delete(ctx, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve resolves given name (not more then three redirects are allowed).
|
||||
func Resolve(name string, typ RecordType) string {
|
||||
// Resolve resolves given name (not more than three redirects are allowed) to a set
|
||||
// of domain records.
|
||||
func Resolve(name string, typ RecordType) []string {
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
return resolve(ctx, name, typ, 2)
|
||||
res := []string{}
|
||||
return resolve(ctx, res, 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)
|
||||
return getAllRecords(ctx, name)
|
||||
}
|
||||
|
||||
// updateBalance updates account's balance and account's tokens.
|
||||
|
@ -482,41 +519,53 @@ func putNameStateWithKey(ctx storage.Context, tokenKey []byte, ns NameState) {
|
|||
storage.Put(ctx, nameKey, nsBytes)
|
||||
}
|
||||
|
||||
// getRecord returns domain record.
|
||||
func getRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType) string {
|
||||
recordKey := getRecordKey(tokenId, name, typ)
|
||||
recBytes := storage.Get(ctx, recordKey)
|
||||
if recBytes == nil {
|
||||
return recBytes.(string) // A hack to actually return NULL.
|
||||
// getRecordsByType returns domain records of the specified type or an empty array if no records found.
|
||||
func getRecordsByType(ctx storage.Context, tokenId []byte, name string, typ RecordType) []string {
|
||||
recordsPrefix := getRecordsByTypePrefix(tokenId, name, typ)
|
||||
records := storage.Find(ctx, recordsPrefix, storage.ValuesOnly|storage.DeserializeValues)
|
||||
res := []string{} // return empty slice if no records was found.
|
||||
for iterator.Next(records) {
|
||||
r := iterator.Value(records).(RecordState)
|
||||
if r.Type == typ {
|
||||
res = append(res, r.Data)
|
||||
}
|
||||
}
|
||||
record := std.Deserialize(recBytes.([]byte)).(RecordState)
|
||||
return record.Data
|
||||
return res
|
||||
}
|
||||
|
||||
// putRecord stores domain record.
|
||||
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, record string) {
|
||||
recordKey := getRecordKey(tokenId, name, typ)
|
||||
// putRecord puts the specified record to the contract storage without any additional checks.
|
||||
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, id byte, data string) {
|
||||
recordKey := getRecordKey(tokenId, name, typ, id)
|
||||
rs := RecordState{
|
||||
Name: name,
|
||||
Type: typ,
|
||||
Data: record,
|
||||
Data: data,
|
||||
ID: id,
|
||||
}
|
||||
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 record with the specified type and ID.
|
||||
// This key always have a single corresponding value.
|
||||
func getRecordKey(tokenId []byte, name string, typ RecordType, id byte) []byte {
|
||||
prefix := getRecordsByTypePrefix(tokenId, name, typ)
|
||||
return append(prefix, id)
|
||||
}
|
||||
|
||||
// getRecordKey returns key used to store domain records.
|
||||
func getRecordKey(tokenId []byte, name string, typ RecordType) []byte {
|
||||
recordKey := getRecordsKey(tokenId, name)
|
||||
// getRecordsByTypePrefix returns prefix used to store domain records with the
|
||||
// specified type of different IDs.
|
||||
func getRecordsByTypePrefix(tokenId []byte, name string, typ RecordType) []byte {
|
||||
recordKey := getRecordsPrefix(tokenId, name)
|
||||
return append(recordKey, []byte{byte(typ)}...)
|
||||
}
|
||||
|
||||
// getRecordsPrefix returns prefix used to store domain records of different types.
|
||||
func getRecordsPrefix(tokenId []byte, name string) []byte {
|
||||
recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...)
|
||||
return append(recordKey, getTokenKey([]byte(name))...)
|
||||
}
|
||||
|
||||
// isValid returns true if the provided address is a valid Uint160.
|
||||
func isValid(address interop.Hash160) bool {
|
||||
return address != nil && len(address) == 20
|
||||
|
@ -713,7 +762,7 @@ func tokenIDFromName(name string) string {
|
|||
|
||||
// resolve resolves provided name using record with the specified type and given
|
||||
// maximum redirections constraint.
|
||||
func resolve(ctx storage.Context, name string, typ RecordType, redirect int) string {
|
||||
func resolve(ctx storage.Context, res []string, name string, typ RecordType, redirect int) []string {
|
||||
if redirect < 0 {
|
||||
panic("invalid redirect")
|
||||
}
|
||||
|
@ -723,33 +772,33 @@ func resolve(ctx storage.Context, name string, typ RecordType, redirect int) str
|
|||
if name[len(name)-1] == '.' {
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
records := getRecords(ctx, name)
|
||||
records := getAllRecords(ctx, name)
|
||||
cname := ""
|
||||
for iterator.Next(records) {
|
||||
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
|
||||
r := iterator.Value(records).(RecordState)
|
||||
if r.Type == typ {
|
||||
res = append(res, r.Data)
|
||||
}
|
||||
if rTyp == byte(CNAME) {
|
||||
cname = value
|
||||
if r.Type == CNAME {
|
||||
cname = r.Data
|
||||
}
|
||||
}
|
||||
if cname == "" {
|
||||
return string([]byte(nil))
|
||||
if cname == "" || typ == CNAME {
|
||||
return res
|
||||
}
|
||||
return resolve(ctx, cname, typ, redirect-1)
|
||||
|
||||
// TODO: the line below must be removed from the neofs nns:
|
||||
// res = append(res, cname)
|
||||
// @roman-khimov, it is done in a separate commit in neofs-contracts repo, is it OK?
|
||||
return resolve(ctx, res, cname, typ, redirect-1)
|
||||
}
|
||||
|
||||
// getRecords returns iterator over the set of records corresponded with the
|
||||
// specified name.
|
||||
func getRecords(ctx storage.Context, name string) iterator.Iterator {
|
||||
// getAllRecords returns iterator over the set of records corresponded with the
|
||||
// specified name. Records returned are of different types and/or different IDs.
|
||||
// No keys are returned.
|
||||
func getAllRecords(ctx storage.Context, name string) iterator.Iterator {
|
||||
tokenID := []byte(tokenIDFromName(name))
|
||||
_ = getNameState(ctx, tokenID)
|
||||
recordsKey := getRecordsKey(tokenID, name)
|
||||
return storage.Find(ctx, recordsKey, storage.DeserializeValues)
|
||||
_ = getNameState(ctx, tokenID) // ensure not expired.
|
||||
recordsPrefix := getRecordsPrefix(tokenID, name)
|
||||
return storage.Find(ctx, recordsPrefix, storage.ValuesOnly|storage.DeserializeValues)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ name: "NameService"
|
|||
sourceurl: https://github.com/nspcc-dev/neo-go/
|
||||
supportedstandards: ["NEP-11"]
|
||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
|
||||
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord",
|
||||
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords",
|
||||
"resolve", "getAllRecords"]
|
||||
events:
|
||||
- name: Transfer
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package nns_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -100,7 +102,7 @@ func TestExpiration(t *testing.T) {
|
|||
|
||||
c.Invoke(t, true, "register", "com", c.CommitteeHash)
|
||||
cAccCommittee.Invoke(t, true, "register", "first.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "first.com", int64(nns.TXT), "sometext")
|
||||
b1 := e.TopBlock(t)
|
||||
|
||||
tx := cAccCommittee.PrepareInvoke(t, "register", "second.com", acc.ScriptHash())
|
||||
|
@ -127,7 +129,7 @@ func TestExpiration(t *testing.T) {
|
|||
require.NoError(t, bc.AddBlock(e.SignBlock(b4)))
|
||||
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true)) // TLD "com" has been expired
|
||||
|
||||
tx = cAcc.PrepareInvoke(t, "getRecord", "first.com", int64(nns.TXT))
|
||||
tx = cAcc.PrepareInvoke(t, "getRecords", "first.com", int64(nns.TXT))
|
||||
b5 := e.NewUnsignedBlock(t, tx)
|
||||
b5.Index = b4.Index + 1
|
||||
b5.PrevHash = b4.Hash()
|
||||
|
@ -208,7 +210,7 @@ func TestRegisterAndRenew(t *testing.T) {
|
|||
c.Invoke(t, props, "properties", "neo.com")
|
||||
}
|
||||
|
||||
func TestSetGetRecord(t *testing.T) {
|
||||
func TestSetAddGetRecord(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
e := c.Executor
|
||||
|
||||
|
@ -217,33 +219,56 @@ func TestSetGetRecord(t *testing.T) {
|
|||
c.Invoke(t, true, "register", "com", c.CommitteeHash)
|
||||
|
||||
t.Run("set before register", func(t *testing.T) {
|
||||
c.InvokeFail(t, "token not found", "setRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||
c.InvokeFail(t, "token not found", "addRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||
})
|
||||
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash)
|
||||
t.Run("invalid parameters", func(t *testing.T) {
|
||||
c.InvokeFail(t, "unsupported record type", "setRecord", "neo.com", int64(0xFF), "1.2.3.4")
|
||||
c.InvokeFail(t, "invalid record", "setRecord", "neo.com", int64(nns.A), "not.an.ip.address")
|
||||
c.InvokeFail(t, "unsupported record type", "addRecord", "neo.com", int64(0xFF), "1.2.3.4")
|
||||
c.InvokeFail(t, "invalid record", "addRecord", "neo.com", int64(nns.A), "not.an.ip.address")
|
||||
})
|
||||
t.Run("invalid witness", func(t *testing.T) {
|
||||
cAcc.InvokeFail(t, "not witnessed by admin", "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cAcc.InvokeFail(t, "not witnessed by admin", "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
})
|
||||
c.Invoke(t, stackitem.Null{}, "getRecord", "neo.com", int64(nns.A))
|
||||
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A))
|
||||
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A))
|
||||
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.AAAA), "2001:0201:1f1f:0000:0000:0100:11a0:11df")
|
||||
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "nspcc.ru")
|
||||
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "getRecords", "neo.com", int64(nns.A))
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "getRecords", "neo.com", int64(nns.A))
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4") // Duplicating record.
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make("1.2.3.4"),
|
||||
stackitem.Make("1.2.3.4"),
|
||||
}), "getRecords", "neo.com", int64(nns.A))
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.AAAA), "2001:0201:1f1f:0000:0000:0100:11a0:11df")
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.CNAME), "nspcc.ru")
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||
// Add multiple records and update some of them.
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext1")
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext2")
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext3")
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make("sometext"),
|
||||
stackitem.Make("sometext1"),
|
||||
stackitem.Make("sometext2"),
|
||||
stackitem.Make("sometext3"),
|
||||
}), "getRecords", "neo.com", int64(nns.TXT))
|
||||
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), 2, "sometext22")
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make("sometext"),
|
||||
stackitem.Make("sometext1"),
|
||||
stackitem.Make("sometext22"),
|
||||
stackitem.Make("sometext3"),
|
||||
}), "getRecords", "neo.com", int64(nns.TXT))
|
||||
|
||||
// Delete record.
|
||||
t.Run("invalid witness", func(t *testing.T) {
|
||||
cAcc.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.CNAME))
|
||||
cAcc.InvokeFail(t, "not witnessed by admin", "deleteRecords", "neo.com", int64(nns.CNAME))
|
||||
})
|
||||
c.Invoke(t, "nspcc.ru", "getRecord", "neo.com", int64(nns.CNAME))
|
||||
c.Invoke(t, stackitem.Null{}, "deleteRecord", "neo.com", int64(nns.CNAME))
|
||||
c.Invoke(t, stackitem.Null{}, "getRecord", "neo.com", int64(nns.CNAME))
|
||||
c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A))
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("nspcc.ru")}), "getRecords", "neo.com", int64(nns.CNAME))
|
||||
c.Invoke(t, stackitem.Null{}, "deleteRecords", "neo.com", int64(nns.CNAME))
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "getRecords", "neo.com", int64(nns.CNAME))
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make("1.2.3.4"),
|
||||
stackitem.Make("1.2.3.4"),
|
||||
}), "getRecords", "neo.com", int64(nns.A))
|
||||
|
||||
t.Run("SetRecord_compatibility", func(t *testing.T) {
|
||||
// tests are got from the NNS C# implementation and changed accordingly to non-native implementation behavior
|
||||
|
@ -303,9 +328,9 @@ func TestSetGetRecord(t *testing.T) {
|
|||
args := []interface{}{"neo.com", int64(testCase.Type), testCase.Name}
|
||||
t.Run(testCase.Name, func(t *testing.T) {
|
||||
if testCase.ShouldFail {
|
||||
c.InvokeFail(t, "", "setRecord", args...)
|
||||
c.InvokeFail(t, "", "addRecord", args...)
|
||||
} else {
|
||||
c.Invoke(t, stackitem.Null{}, "setRecord", args...)
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", args...)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -343,15 +368,15 @@ func TestSetAdmin(t *testing.T) {
|
|||
c.Invoke(t, props, "properties", "neo.com")
|
||||
|
||||
t.Run("set and delete by admin", func(t *testing.T) {
|
||||
cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||
cGuest.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.TXT))
|
||||
cAdmin.Invoke(t, stackitem.Null{}, "deleteRecord", "neo.com", int64(nns.TXT))
|
||||
cAdmin.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||
cGuest.InvokeFail(t, "not witnessed by admin", "deleteRecords", "neo.com", int64(nns.TXT))
|
||||
cAdmin.Invoke(t, stackitem.Null{}, "deleteRecords", "neo.com", int64(nns.TXT))
|
||||
})
|
||||
|
||||
t.Run("set admin to null", func(t *testing.T) {
|
||||
cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||
cAdmin.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||
cOwner.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", nil)
|
||||
cAdmin.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.TXT))
|
||||
cAdmin.InvokeFail(t, "not witnessed by admin", "deleteRecords", "neo.com", int64(nns.TXT))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -367,7 +392,7 @@ func TestTransfer(t *testing.T) {
|
|||
|
||||
c.Invoke(t, true, "register", "com", c.CommitteeHash)
|
||||
cFromCommittee.Invoke(t, true, "register", "neo.com", from.ScriptHash())
|
||||
cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cFrom.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cFrom.InvokeFail(t, "token not found", "transfer", to.ScriptHash(), "not.exists", nil)
|
||||
c.Invoke(t, false, "transfer", to.ScriptHash(), "neo.com", nil)
|
||||
cFrom.Invoke(t, true, "transfer", to.ScriptHash(), "neo.com", nil)
|
||||
|
@ -450,18 +475,27 @@ func TestResolve(t *testing.T) {
|
|||
|
||||
c.Invoke(t, true, "register", "com", c.CommitteeHash)
|
||||
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.CNAME), "alias.com")
|
||||
|
||||
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(nns.TXT), "sometxt from alias1")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(nns.CNAME), "alias2.com")
|
||||
|
||||
c.Invoke(t, "1.2.3.4", "resolve", "neo.com", int64(nns.A))
|
||||
c.Invoke(t, "alias.com", "resolve", "neo.com", int64(nns.CNAME))
|
||||
c.Invoke(t, "sometxt", "resolve", "neo.com", int64(nns.TXT))
|
||||
c.Invoke(t, "sometxt", "resolve", "neo.com.", int64(nns.TXT))
|
||||
c.InvokeFail(t, "invalid domain name format", "resolve", "neo.com..", int64(nns.TXT))
|
||||
c.Invoke(t, stackitem.Null{}, "resolve", "neo.com", int64(nns.AAAA))
|
||||
cAccCommittee.Invoke(t, true, "register", "alias2.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias2.com", int64(nns.TXT), "sometxt from alias2")
|
||||
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "resolve", "neo.com", int64(nns.A))
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "resolve", "neo.com.", int64(nns.A))
|
||||
c.InvokeFail(t, "invalid domain name format", "resolve", "neo.com..", int64(nns.A))
|
||||
|
||||
// Check CNAME is properly resolved and is not included into the result.
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("sometxt from alias1"), stackitem.Make("sometxt from alias2")}), "resolve", "neo.com", int64(nns.TXT))
|
||||
// Check CNAME is included into the result and is not resolved.
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("alias.com")}), "resolve", "neo.com", int64(nns.CNAME))
|
||||
|
||||
// Empty result.
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "resolve", "neo.com", int64(nns.AAAA))
|
||||
}
|
||||
|
||||
func TestGetAllRecords(t *testing.T) {
|
||||
|
@ -474,14 +508,14 @@ func TestGetAllRecords(t *testing.T) {
|
|||
|
||||
c.Invoke(t, true, "register", "com", c.CommitteeHash)
|
||||
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "bla0")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "bla1") // overwrite
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.CNAME), "alias.com")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "bla0")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), 0, "bla1") // overwrite
|
||||
|
||||
// Add some arbitrary data.
|
||||
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(nns.TXT), "sometxt")
|
||||
|
||||
script, err := smartcontract.CreateCallAndUnwrapIteratorScript(c.Hash, "getAllRecords", 10, "neo.com")
|
||||
require.NoError(t, err)
|
||||
|
@ -491,21 +525,63 @@ func TestGetAllRecords(t *testing.T) {
|
|||
stackitem.NewByteArray([]byte("neo.com")),
|
||||
stackitem.Make(nns.A),
|
||||
stackitem.NewByteArray([]byte("1.2.3.4")),
|
||||
stackitem.NewBigInteger(big.NewInt(0)),
|
||||
}),
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewByteArray([]byte("neo.com")),
|
||||
stackitem.Make(nns.CNAME),
|
||||
stackitem.NewByteArray([]byte("alias.com")),
|
||||
stackitem.NewBigInteger(big.NewInt(0)),
|
||||
}),
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewByteArray([]byte("neo.com")),
|
||||
stackitem.Make(nns.TXT),
|
||||
stackitem.NewByteArray([]byte("bla1")),
|
||||
stackitem.NewBigInteger(big.NewInt(0)),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
func TestGetRecords(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
e := c.Executor
|
||||
|
||||
acc := e.NewAccount(t)
|
||||
cAcc := c.WithSigners(acc)
|
||||
cAccCommittee := c.WithSigners(acc, c.Committee)
|
||||
|
||||
c.Invoke(t, true, "register", "com", c.CommitteeHash)
|
||||
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.CNAME), "alias.com")
|
||||
|
||||
// Add some arbitrary data.
|
||||
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(nns.TXT), "sometxt")
|
||||
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "getRecords", "neo.com", int64(nns.A))
|
||||
// Check empty result of `getRecords`.
|
||||
c.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "getRecords", "neo.com", int64(nns.AAAA))
|
||||
}
|
||||
|
||||
func TestNNSAddRecord(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
cAccCommittee := c.WithSigners(c.Committee)
|
||||
|
||||
c.Invoke(t, true, "register", "com", c.CommitteeHash)
|
||||
cAccCommittee.Invoke(t, true, "register", "neo.com", c.CommitteeHash)
|
||||
|
||||
for i := 0; i <= maxRecordID+1; i++ {
|
||||
if i == maxRecordID+1 {
|
||||
c.InvokeFail(t, "maximum number of records reached", "addRecord", "neo.com", int64(nns.TXT), strconv.Itoa(i))
|
||||
} else {
|
||||
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
defaultNameServiceDomainPrice = 10_0000_0000
|
||||
defaultNameServiceSysfee = 6000_0000
|
||||
maxRecordID = 255
|
||||
)
|
||||
|
|
|
@ -177,7 +177,7 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) {
|
|||
t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID))
|
||||
|
||||
// Block #15: set A record type with priv0 owner via NNS.
|
||||
nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15
|
||||
nsPriv0Invoker.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15
|
||||
|
||||
// Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
|
||||
txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", "newtestvalue") // tx1
|
||||
|
|
Loading…
Reference in a new issue