Compare commits
16 commits
Author | SHA1 | Date | |
---|---|---|---|
|
14c9858df2 | ||
|
d8e0a02a86 | ||
|
d5b1c0e429 | ||
|
db9cea5ecc | ||
|
ea934b8e30 | ||
|
8790602f69 | ||
|
c9050cef4b | ||
|
c296f8804c | ||
|
4543de0923 | ||
|
d77b35c385 | ||
|
225152f2d7 | ||
|
baf24d1c66 | ||
|
017a6b9bc1 | ||
|
5cb2a1219c | ||
|
c11481b119 | ||
|
bd3722041a |
8 changed files with 665 additions and 243 deletions
|
@ -47,22 +47,27 @@ const (
|
|||
maxRegisterPrice = 1_0000_0000_0000
|
||||
// maxRootLength is the maximum domain root length.
|
||||
maxRootLength = 16
|
||||
// maxDomainNameFragmentLength is the maximum length of the domain name fragment.
|
||||
maxDomainNameFragmentLength = 62
|
||||
// maxDomainNameFragmentLength is the maximum length of the domain name fragment
|
||||
maxDomainNameFragmentLength = 63
|
||||
// minDomainNameLength is minimum domain length.
|
||||
minDomainNameLength = 3
|
||||
// maxDomainNameLength is maximum domain length.
|
||||
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.
|
||||
const (
|
||||
// defaultRegisterPrice is the default price for new domain registration.
|
||||
defaultRegisterPrice = 10_0000_0000
|
||||
// millisecondsInSecond is the amount of milliseconds per second.
|
||||
millisecondsInSecond = 1000
|
||||
// millisecondsInYear is amount of milliseconds per year.
|
||||
millisecondsInYear = 365 * 24 * 3600 * 1000
|
||||
millisecondsInYear = 365 * 24 * 3600 * millisecondsInSecond
|
||||
)
|
||||
|
||||
// RecordState is a type that registered entities are saved to.
|
||||
|
@ -70,6 +75,7 @@ type RecordState struct {
|
|||
Name string
|
||||
Type RecordType
|
||||
Data string
|
||||
ID byte
|
||||
}
|
||||
|
||||
// Update updates NameService contract.
|
||||
|
@ -118,6 +124,7 @@ func Properties(tokenID []byte) map[string]interface{} {
|
|||
return map[string]interface{}{
|
||||
"name": ns.Name,
|
||||
"expiration": ns.Expiration,
|
||||
"admin": ns.Admin,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,22 +186,6 @@ func Transfer(to interop.Hash160, tokenID []byte, data interface{}) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// AddRoot registers new root.
|
||||
func AddRoot(root string) {
|
||||
checkCommittee()
|
||||
if !checkFragment(root, true) {
|
||||
panic("invalid root format")
|
||||
}
|
||||
var (
|
||||
ctx = storage.GetContext()
|
||||
rootKey = append([]byte{prefixRoot}, []byte(root)...)
|
||||
)
|
||||
if storage.Get(ctx, rootKey) != nil {
|
||||
panic("root already exists")
|
||||
}
|
||||
storage.Put(ctx, rootKey, 0)
|
||||
}
|
||||
|
||||
// Roots returns iterator over a set of NameService roots.
|
||||
func Roots() iterator.Iterator {
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
|
@ -219,31 +210,94 @@ func GetPrice() int {
|
|||
|
||||
// IsAvailable checks whether provided domain name is available.
|
||||
func IsAvailable(name string) bool {
|
||||
fragments := splitAndCheck(name, false)
|
||||
fragments := splitAndCheck(name, true)
|
||||
if fragments == nil {
|
||||
panic("invalid domain name format")
|
||||
}
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil {
|
||||
panic("root not found")
|
||||
}
|
||||
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...))
|
||||
if nsBytes == nil {
|
||||
l := len(fragments)
|
||||
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil {
|
||||
if l != 1 {
|
||||
panic("TLD not found")
|
||||
}
|
||||
return true
|
||||
}
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
return runtime.GetTime() >= ns.Expiration
|
||||
if !parentExpired(ctx, 0, fragments) {
|
||||
return false
|
||||
}
|
||||
return len(getParentConflictingRecord(ctx, name, fragments)) == 0
|
||||
}
|
||||
|
||||
// getPrentConflictingRecord returns record of '*.name' format if they are presented.
|
||||
// These records conflict with domain name to be registered.
|
||||
func getParentConflictingRecord(ctx storage.Context, name string, fragments []string) string {
|
||||
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
|
||||
parentRecKey := append([]byte{prefixRecord}, parentKey...)
|
||||
it := storage.Find(ctx, parentRecKey, storage.ValuesOnly|storage.DeserializeValues)
|
||||
suffix := []byte(name)
|
||||
for iterator.Next(it) {
|
||||
r := iterator.Value(it).(RecordState)
|
||||
ind := std.MemorySearchLastIndex([]byte(r.Name), suffix, len(r.Name))
|
||||
if ind > 0 && ind+len(suffix) == len(r.Name) {
|
||||
return r.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// parentExpired returns true if any domain from fragments doesn't exist or expired.
|
||||
// first denotes the deepest subdomain to check.
|
||||
func parentExpired(ctx storage.Context, first int, fragments []string) bool {
|
||||
now := runtime.GetTime()
|
||||
last := len(fragments) - 1
|
||||
name := fragments[last]
|
||||
for i := last; i >= first; i-- {
|
||||
if i != last {
|
||||
name = fragments[i] + "." + name
|
||||
}
|
||||
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...))
|
||||
if nsBytes == nil {
|
||||
return true
|
||||
}
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
if now >= ns.Expiration {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Register registers new domain with the specified owner and name if it's available.
|
||||
func Register(name string, owner interop.Hash160) bool {
|
||||
fragments := splitAndCheck(name, false)
|
||||
func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
|
||||
fragments := splitAndCheck(name, true)
|
||||
if fragments == nil {
|
||||
panic("invalid domain name format")
|
||||
}
|
||||
l := len(fragments)
|
||||
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...)
|
||||
ctx := storage.GetContext()
|
||||
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil {
|
||||
panic("root not found")
|
||||
tldBytes := storage.Get(ctx, tldKey)
|
||||
if l == 1 {
|
||||
checkCommittee()
|
||||
if tldBytes != nil {
|
||||
panic("TLD already exists")
|
||||
}
|
||||
storage.Put(ctx, tldKey, 0)
|
||||
} else {
|
||||
if tldBytes == nil {
|
||||
panic("TLD not found")
|
||||
}
|
||||
if parentExpired(ctx, 1, fragments) {
|
||||
panic("one of the parent domains is not registered")
|
||||
}
|
||||
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
|
||||
nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...))
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
ns.checkAdmin()
|
||||
|
||||
if conflict := getParentConflictingRecord(ctx, name, fragments); len(conflict) != 0 {
|
||||
panic("parent domain has conflicting records: " + conflict)
|
||||
}
|
||||
}
|
||||
|
||||
if !isValid(owner) {
|
||||
|
@ -275,14 +329,62 @@ func Register(name string, owner interop.Hash160) bool {
|
|||
ns := NameState{
|
||||
Owner: owner,
|
||||
Name: name,
|
||||
Expiration: runtime.GetTime() + millisecondsInYear,
|
||||
Expiration: runtime.GetTime() + expire*millisecondsInSecond,
|
||||
}
|
||||
putNameStateWithKey(ctx, tokenKey, ns)
|
||||
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
|
||||
updateBalance(ctx, []byte(name), owner, +1)
|
||||
postTransfer(oldOwner, owner, []byte(name), nil)
|
||||
return true
|
||||
}
|
||||
|
||||
// UpdateSOA updates soa record.
|
||||
func UpdateSOA(name, email string, refresh, retry, expire, ttl int) {
|
||||
if len(name) > maxDomainNameLength {
|
||||
panic("invalid domain name format")
|
||||
}
|
||||
ctx := storage.GetContext()
|
||||
ns := getNameState(ctx, []byte(name))
|
||||
ns.checkAdmin()
|
||||
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
|
||||
}
|
||||
|
||||
// putSoaRecord stores SOA domain record.
|
||||
func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expire, ttl int) {
|
||||
data := name + " " + email + " " +
|
||||
std.Itoa(runtime.GetTime(), 10) + " " +
|
||||
std.Itoa(refresh, 10) + " " +
|
||||
std.Itoa(retry, 10) + " " +
|
||||
std.Itoa(expire, 10) + " " +
|
||||
std.Itoa(ttl, 10)
|
||||
tokenId := []byte(tokenIDFromName(ctx, name))
|
||||
putRecord(ctx, tokenId, name, SOA, 0, data)
|
||||
}
|
||||
|
||||
// updateSoaSerial updates serial of the corresponding SOA domain record.
|
||||
func updateSoaSerial(ctx storage.Context, tokenId []byte) {
|
||||
recordKey := getRecordKey(tokenId, string(tokenId), SOA, 0)
|
||||
|
||||
recBytes := storage.Get(ctx, recordKey)
|
||||
if recBytes == nil {
|
||||
panic("SOA record not found")
|
||||
}
|
||||
rec := std.Deserialize(recBytes.([]byte)).(RecordState)
|
||||
|
||||
split := std.StringSplitNonEmpty(rec.Data, " ")
|
||||
if len(split) != 7 {
|
||||
panic("corrupted SOA record format")
|
||||
}
|
||||
split[2] = std.Itoa(runtime.GetTime(), 10) // update serial
|
||||
rec.Data = split[0] + " " + split[1] + " " +
|
||||
split[2] + " " + split[3] + " " +
|
||||
split[4] + " " + split[5] + " " +
|
||||
split[6]
|
||||
|
||||
recBytes = std.Serialize(rec)
|
||||
storage.Put(ctx, recordKey, recBytes)
|
||||
}
|
||||
|
||||
// Renew increases domain expiration date.
|
||||
func Renew(name string) int {
|
||||
if len(name) > maxDomainNameLength {
|
||||
|
@ -313,9 +415,46 @@ 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) {
|
||||
tokenID := []byte(tokenIDFromName(name))
|
||||
// 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)
|
||||
updateSoaSerial(ctx, tokenID)
|
||||
}
|
||||
|
||||
// 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.ValuesOnly|storage.DeserializeValues)
|
||||
for iterator.Next(records) {
|
||||
r := iterator.Value(records).(RecordState)
|
||||
if r.Name == name && r.Type == typ && r.Data == data {
|
||||
panic("record already exists")
|
||||
}
|
||||
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)
|
||||
updateSoaSerial(ctx, tokenID)
|
||||
}
|
||||
|
||||
// checkRecord performs record validness check and returns token ID.
|
||||
func checkRecord(ctx storage.Context, name string, typ RecordType, data string) []byte {
|
||||
tokenID := []byte(tokenIDFromName(ctx, name))
|
||||
var ok bool
|
||||
switch typ {
|
||||
case A:
|
||||
|
@ -332,44 +471,50 @@ 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 {
|
||||
tokenID := []byte(tokenIDFromName(name))
|
||||
// GetRecords returns domain records of the specified type if they exist or an empty
|
||||
// array if not.
|
||||
func GetRecords(name string, typ RecordType) []string {
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
tokenID := []byte(tokenIDFromName(ctx, name))
|
||||
_ = 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) {
|
||||
tokenID := []byte(tokenIDFromName(name))
|
||||
// DeleteRecords removes all domain records with the specified type.
|
||||
func DeleteRecords(name string, typ RecordType) {
|
||||
if typ == SOA {
|
||||
panic("forbidden to delete SOA record")
|
||||
}
|
||||
ctx := storage.GetContext()
|
||||
tokenID := []byte(tokenIDFromName(ctx, name))
|
||||
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)
|
||||
}
|
||||
updateSoaSerial(ctx, tokenID)
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -425,12 +570,17 @@ func getTokenKey(tokenID []byte) []byte {
|
|||
// getNameState returns domain name state by the specified tokenID.
|
||||
func getNameState(ctx storage.Context, tokenID []byte) NameState {
|
||||
tokenKey := getTokenKey(tokenID)
|
||||
return getNameStateWithKey(ctx, tokenKey)
|
||||
ns := getNameStateWithKey(ctx, tokenKey)
|
||||
fragments := std.StringSplit(string(tokenID), ".")
|
||||
if parentExpired(ctx, 1, fragments) {
|
||||
panic("parent domain has expired")
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
// getNameStateWithKey returns domain name state by the specified token key.
|
||||
func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState {
|
||||
nameKey := append([]byte{prefixName}, tokenKey...)
|
||||
nameKey := getNameStateKey(tokenKey)
|
||||
nsBytes := storage.Get(ctx, nameKey)
|
||||
if nsBytes == nil {
|
||||
panic("token not found")
|
||||
|
@ -440,6 +590,11 @@ func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState {
|
|||
return ns
|
||||
}
|
||||
|
||||
// getNameStateKey returns NameState key for the provided token key.
|
||||
func getNameStateKey(tokenKey []byte) []byte {
|
||||
return append([]byte{prefixName}, tokenKey...)
|
||||
}
|
||||
|
||||
// putNameState stores domain name state.
|
||||
func putNameState(ctx storage.Context, ns NameState) {
|
||||
tokenKey := getTokenKey([]byte(ns.Name))
|
||||
|
@ -453,41 +608,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
|
||||
|
@ -507,6 +674,8 @@ func checkCommittee() {
|
|||
}
|
||||
|
||||
// checkFragment validates root or a part of domain name.
|
||||
// 1. Root domain must start with a letter.
|
||||
// 2. All other fragments must start and end in a letter or a digit.
|
||||
func checkFragment(v string, isRoot bool) bool {
|
||||
maxLength := maxDomainNameFragmentLength
|
||||
if isRoot {
|
||||
|
@ -525,12 +694,12 @@ func checkFragment(v string, isRoot bool) bool {
|
|||
return false
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(v); i++ {
|
||||
if !isAlNum(v[i]) {
|
||||
for i := 1; i < len(v)-1; i++ {
|
||||
if v[i] != '-' && !isAlNum(v[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return isAlNum(v[len(v)-1])
|
||||
}
|
||||
|
||||
// isAlNum checks whether provided char is a lowercase letter or a number.
|
||||
|
@ -546,9 +715,6 @@ func splitAndCheck(name string, allowMultipleFragments bool) []string {
|
|||
}
|
||||
fragments := std.StringSplit(name, ".")
|
||||
l = len(fragments)
|
||||
if l < 2 {
|
||||
return nil
|
||||
}
|
||||
if l > 2 && !allowMultipleFragments {
|
||||
return nil
|
||||
}
|
||||
|
@ -671,48 +837,66 @@ func checkIPv6(data string) bool {
|
|||
}
|
||||
|
||||
// tokenIDFromName returns token ID (domain.root) from provided name.
|
||||
func tokenIDFromName(name string) string {
|
||||
func tokenIDFromName(ctx storage.Context, name string) string {
|
||||
fragments := splitAndCheck(name, true)
|
||||
if fragments == nil {
|
||||
panic("invalid domain name format")
|
||||
}
|
||||
l := len(fragments)
|
||||
return name[len(name)-(len(fragments[l-1])+len(fragments[l-2])+1):]
|
||||
sum := 0
|
||||
for i := 0; i < len(fragments)-1; i++ {
|
||||
tokenKey := getTokenKey([]byte(name[sum:]))
|
||||
nameKey := getNameStateKey(tokenKey)
|
||||
nsBytes := storage.Get(ctx, nameKey)
|
||||
if nsBytes != nil {
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
if runtime.GetTime() < ns.Expiration {
|
||||
return name[sum:]
|
||||
}
|
||||
}
|
||||
sum += len(fragments[i]) + 1
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
records := getRecords(ctx, name)
|
||||
if len(name) == 0 {
|
||||
panic("invalid name")
|
||||
}
|
||||
if name[len(name)-1] == '.' {
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
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 {
|
||||
tokenID := []byte(tokenIDFromName(name))
|
||||
_ = getNameState(ctx, tokenID)
|
||||
recordsKey := getRecordsKey(tokenID, name)
|
||||
return storage.Find(ctx, recordsKey, storage.DeserializeValues)
|
||||
// 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(ctx, name))
|
||||
_ = 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"
|
||||
|
||||
|
@ -9,18 +11,30 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newNSClient(t *testing.T) *neotest.ContractInvoker {
|
||||
const (
|
||||
millisecondsInYear = 365 * 24 * 3600 * 1000
|
||||
maxDomainNameFragmentLength = 63
|
||||
)
|
||||
|
||||
func newNSClient(t *testing.T, registerComTLD bool) *neotest.ContractInvoker {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
c := neotest.CompileFile(t, e.CommitteeHash, ".", "nns.yml")
|
||||
e.DeployContract(t, c, nil)
|
||||
ctr := neotest.CompileFile(t, e.CommitteeHash, ".", "nns.yml")
|
||||
e.DeployContract(t, ctr, nil)
|
||||
|
||||
return e.CommitteeInvoker(c.Hash)
|
||||
c := e.CommitteeInvoker(ctr.Hash)
|
||||
if registerComTLD {
|
||||
// Set expiration big enough to pass all tests.
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
c.Invoke(t, true, "register", "com", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func TestNameService_Price(t *testing.T) {
|
||||
|
@ -29,7 +43,7 @@ func TestNameService_Price(t *testing.T) {
|
|||
maxPrice = int64(10000_00000000)
|
||||
)
|
||||
|
||||
c := newNSClient(t)
|
||||
c := newNSClient(t, false)
|
||||
|
||||
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||
acc := c.NewAccount(t)
|
||||
|
@ -62,7 +76,7 @@ func TestNameService_Price(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNonfungible(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
c := newNSClient(t, false)
|
||||
|
||||
c.Signers = []neotest.Signer{c.NewAccount(t)}
|
||||
c.Invoke(t, "NNS", "symbol")
|
||||
|
@ -70,104 +84,109 @@ func TestNonfungible(t *testing.T) {
|
|||
c.Invoke(t, 0, "totalSupply")
|
||||
}
|
||||
|
||||
func TestAddRoot(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
func TestRegisterTLD(t *testing.T) {
|
||||
c := newNSClient(t, false)
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
t.Run("invalid format", func(t *testing.T) {
|
||||
c.InvokeFail(t, "invalid root format", "addRoot", "")
|
||||
c.InvokeFail(t, "invalid domain name format", "register", "", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
})
|
||||
t.Run("not signed by committee", func(t *testing.T) {
|
||||
acc := c.NewAccount(t)
|
||||
c := c.WithSigners(acc)
|
||||
c.InvokeFail(t, "not witnessed by committee", "addRoot", "some")
|
||||
c.InvokeFail(t, "not witnessed by committee", "register", "some", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
})
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "some")
|
||||
c.Invoke(t, true, "register", "some", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
t.Run("already exists", func(t *testing.T) {
|
||||
c.InvokeFail(t, "already exists", "addRoot", "some")
|
||||
c.InvokeFail(t, "TLD already exists", "register", "some", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExpiration(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
c := newNSClient(t, true)
|
||||
e := c.Executor
|
||||
bc := e.Chain
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
acc := e.NewAccount(t)
|
||||
cAcc := c.WithSigners(acc)
|
||||
cAccCommittee := c.WithSigners(acc, c.Committee) // acc + committee signers for ".com"'s subdomains registration
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
|
||||
cAccCommittee.Invoke(t, true, "register", "first.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "first.com", int64(nns.TXT), "sometext")
|
||||
b1 := e.TopBlock(t)
|
||||
|
||||
tx := cAcc.PrepareInvoke(t, "register", "second.com", acc.ScriptHash())
|
||||
tx := cAccCommittee.PrepareInvoke(t, "register", "second.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
b2 := e.NewUnsignedBlock(t, tx)
|
||||
b2.Index = b1.Index + 1
|
||||
b2.PrevHash = b1.Hash()
|
||||
b2.Timestamp = b1.Timestamp + 10000
|
||||
require.NoError(t, bc.AddBlock(e.SignBlock(b2)))
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
|
||||
tx = cAcc.PrepareInvoke(t, "isAvailable", "first.com")
|
||||
b3 := e.NewUnsignedBlock(t, tx)
|
||||
b3.Index = b2.Index + 1
|
||||
b3.PrevHash = b2.Hash()
|
||||
b3.Timestamp = b1.Timestamp + (millisecondsInYear + 1)
|
||||
require.NoError(t, bc.AddBlock(e.SignBlock(b3)))
|
||||
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true))
|
||||
|
||||
tx = cAcc.PrepareInvoke(t, "isAvailable", "second.com")
|
||||
b4 := e.NewUnsignedBlock(t, tx)
|
||||
b4.Index = b3.Index + 1
|
||||
b4.PrevHash = b3.Hash()
|
||||
b4.Timestamp = b3.Timestamp + 1000
|
||||
require.NoError(t, bc.AddBlock(e.SignBlock(b4)))
|
||||
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(false))
|
||||
b3 := e.NewUnsignedBlock(t)
|
||||
b3.Index = b2.Index + 1
|
||||
b3.PrevHash = b2.Hash()
|
||||
b3.Timestamp = b1.Timestamp + (uint64(expire) * 1000)
|
||||
require.NoError(t, bc.AddBlock(e.SignBlock(b3)))
|
||||
|
||||
tx = cAcc.PrepareInvoke(t, "getRecord", "first.com", int64(nns.TXT))
|
||||
b5 := e.NewUnsignedBlock(t, tx)
|
||||
b5.Index = b4.Index + 1
|
||||
b5.PrevHash = b4.Hash()
|
||||
b5.Timestamp = b4.Timestamp + 1000
|
||||
require.NoError(t, bc.AddBlock(e.SignBlock(b5)))
|
||||
e.CheckFault(t, tx.Hash(), "name has expired")
|
||||
cAcc.Invoke(t, true, "isAvailable", "first.com") // "first.com" has been expired
|
||||
cAcc.Invoke(t, true, "isAvailable", "second.com") // TLD "com" has been expired
|
||||
cAcc.InvokeFail(t, "name has expired", "getRecords", "first.com", int64(nns.TXT))
|
||||
|
||||
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash()) // Re-register.
|
||||
cAcc.Invoke(t, stackitem.Null{}, "resolve", "first.com", int64(nns.TXT))
|
||||
// TODO: According to the new code, we can't re-register expired "com" TLD, because it's already registered; at the
|
||||
// same time we can't renew it because it's already expired. We likely need to change this logic in the contract and
|
||||
// after that uncomment the lines below.
|
||||
// c.Invoke(t, true, "renew", "com")
|
||||
// cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash()) // Re-register.
|
||||
// cAcc.Invoke(t, stackitem.Null{}, "resolve", "first.com", int64(nns.TXT))
|
||||
}
|
||||
|
||||
const millisecondsInYear = 365 * 24 * 3600 * 1000
|
||||
|
||||
func TestRegisterAndRenew(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
c := newNSClient(t, false)
|
||||
e := c.Executor
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "org")
|
||||
c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
|
||||
c.Invoke(t, true, "register", "org", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
|
||||
c.Invoke(t, true, "register", "com", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.Invoke(t, true, "isAvailable", "neo.com")
|
||||
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash)
|
||||
c.InvokeFail(t, "invalid domain name format", "register", "docs.neo.org", e.CommitteeHash)
|
||||
c.InvokeFail(t, "invalid domain name format", "register", "\nneo.com'", e.CommitteeHash)
|
||||
c.InvokeFail(t, "invalid domain name format", "register", "neo.com\n", e.CommitteeHash)
|
||||
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash)
|
||||
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceDomainPrice, "register", "neo.com", e.CommitteeHash)
|
||||
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.InvokeFail(t, "one of the parent domains is not registered", "register", "docs.neo.org", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.InvokeFail(t, "invalid domain name format", "register", "\nneo.com'", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.InvokeFail(t, "invalid domain name format", "register", "neo.com\n", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceDomainPrice, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
var maxLenFragment string
|
||||
for i := 0; i < maxDomainNameFragmentLength; i++ {
|
||||
maxLenFragment += "q"
|
||||
}
|
||||
c.Invoke(t, true, "isAvailable", maxLenFragment+".com")
|
||||
c.Invoke(t, true, "register", maxLenFragment+".com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.InvokeFail(t, "invalid domain name format", "register", maxLenFragment+"q.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
|
||||
c.Invoke(t, true, "isAvailable", "neo.com")
|
||||
c.Invoke(t, 0, "balanceOf", e.CommitteeHash)
|
||||
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash)
|
||||
c.Invoke(t, 3, "balanceOf", e.CommitteeHash) // org, com, qqq...qqq.com
|
||||
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
topBlock := e.TopBlock(t)
|
||||
expectedExpiration := topBlock.Timestamp + millisecondsInYear
|
||||
c.Invoke(t, false, "register", "neo.com", e.CommitteeHash)
|
||||
expectedExpiration := topBlock.Timestamp + uint64(expire*1000)
|
||||
c.Invoke(t, false, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.Invoke(t, false, "isAvailable", "neo.com")
|
||||
|
||||
t.Run("domain names with hyphen", func(t *testing.T) {
|
||||
c.InvokeFail(t, "invalid domain name format", "register", "-testdomain.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.InvokeFail(t, "invalid domain name format", "register", "testdomain-.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
c.Invoke(t, true, "register", "test-domain.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
})
|
||||
|
||||
props := stackitem.NewMap()
|
||||
props.Add(stackitem.Make("name"), stackitem.Make("neo.com"))
|
||||
props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration))
|
||||
props.Add(stackitem.Make("admin"), stackitem.Null{}) // no admin was set
|
||||
c.Invoke(t, props, "properties", "neo.com")
|
||||
c.Invoke(t, 1, "balanceOf", e.CommitteeHash)
|
||||
c.Invoke(t, 5, "balanceOf", e.CommitteeHash) // org, com, qqq...qqq.com, neo.com, test-domain.com
|
||||
c.Invoke(t, e.CommitteeHash.BytesBE(), "ownerOf", []byte("neo.com"))
|
||||
|
||||
t.Run("invalid token ID", func(t *testing.T) {
|
||||
|
@ -185,42 +204,59 @@ func TestRegisterAndRenew(t *testing.T) {
|
|||
c.Invoke(t, props, "properties", "neo.com")
|
||||
}
|
||||
|
||||
func TestSetGetRecord(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
func TestSetAddGetRecord(t *testing.T) {
|
||||
c := newNSClient(t, true)
|
||||
e := c.Executor
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
acc := e.NewAccount(t)
|
||||
cAcc := c.WithSigners(acc)
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
|
||||
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)
|
||||
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
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.InvokeFail(t, "record already exists", "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")}), "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")}), "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
|
||||
|
@ -280,9 +316,10 @@ 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...)
|
||||
c.Invoke(t, stackitem.Null{}, "deleteRecords", "neo.com", int64(testCase.Type)) // clear records after test to avoid duplicating records.
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -290,19 +327,21 @@ func TestSetGetRecord(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSetAdmin(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
c := newNSClient(t, true)
|
||||
e := c.Executor
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
owner := e.NewAccount(t)
|
||||
cOwner := c.WithSigners(owner)
|
||||
cOwnerCommittee := c.WithSigners(owner, c.Committee)
|
||||
admin := e.NewAccount(t)
|
||||
cAdmin := c.WithSigners(admin)
|
||||
guest := e.NewAccount(t)
|
||||
cGuest := c.WithSigners(guest)
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
|
||||
cOwner.Invoke(t, true, "register", "neo.com", owner.ScriptHash())
|
||||
cOwner.InvokeFail(t, "not witnessed by admin", "register", "neo.com", owner.ScriptHash(), mail, refresh, retry, expire, ttl) // admin is committee
|
||||
cOwnerCommittee.Invoke(t, true, "register", "neo.com", owner.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
expectedExpiration := e.TopBlock(t).Timestamp + uint64(expire)*1000
|
||||
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.ScriptHash())
|
||||
|
||||
// Must be witnessed by both owner and admin.
|
||||
|
@ -310,36 +349,42 @@ func TestSetAdmin(t *testing.T) {
|
|||
cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.ScriptHash())
|
||||
cc := c.WithSigners(owner, admin)
|
||||
cc.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", admin.ScriptHash())
|
||||
props := stackitem.NewMap()
|
||||
props.Add(stackitem.Make("name"), stackitem.Make("neo.com"))
|
||||
props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration))
|
||||
props.Add(stackitem.Make("admin"), stackitem.Make(admin.ScriptHash().BytesBE()))
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTransfer(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
c := newNSClient(t, true)
|
||||
e := c.Executor
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
from := e.NewAccount(t)
|
||||
cFrom := c.WithSigners(from)
|
||||
cFromCommittee := c.WithSigners(from, c.Committee)
|
||||
to := e.NewAccount(t)
|
||||
cTo := c.WithSigners(to)
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
cFrom.Invoke(t, true, "register", "neo.com", from.ScriptHash())
|
||||
cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cFromCommittee.Invoke(t, true, "register", "neo.com", from.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
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)
|
||||
cFrom.Invoke(t, 1, "totalSupply")
|
||||
cFrom.Invoke(t, 2, "totalSupply") // com, neo.com
|
||||
cFrom.Invoke(t, to.ScriptHash().BytesBE(), "ownerOf", "neo.com")
|
||||
|
||||
// without onNEP11Transfer
|
||||
|
@ -358,30 +403,32 @@ func TestTransfer(t *testing.T) {
|
|||
&compiler.Options{Name: "foo"})
|
||||
e.DeployContract(t, ctr, nil)
|
||||
cTo.Invoke(t, true, "transfer", ctr.Hash, []byte("neo.com"), nil)
|
||||
cFrom.Invoke(t, 1, "totalSupply")
|
||||
cFrom.Invoke(t, 2, "totalSupply") // com, neo.com
|
||||
cFrom.Invoke(t, ctr.Hash.BytesBE(), "ownerOf", []byte("neo.com"))
|
||||
}
|
||||
|
||||
func TestTokensOf(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
c := newNSClient(t, false)
|
||||
e := c.Executor
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
acc1 := e.NewAccount(t)
|
||||
cAcc1 := c.WithSigners(acc1)
|
||||
cAcc1Committee := c.WithSigners(acc1, c.Committee)
|
||||
acc2 := e.NewAccount(t)
|
||||
cAcc2 := c.WithSigners(acc2)
|
||||
cAcc2Committee := c.WithSigners(acc2, c.Committee)
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
cAcc1.Invoke(t, true, "register", "neo.com", acc1.ScriptHash())
|
||||
cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash())
|
||||
tld := []byte("com")
|
||||
c.Invoke(t, true, "register", tld, c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
cAcc1Committee.Invoke(t, true, "register", "neo.com", acc1.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
cAcc2Committee.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
|
||||
testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
|
||||
testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
|
||||
testTokensOf(t, c, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
|
||||
testTokensOf(t, c, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still
|
||||
testTokensOf(t, c, tld, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
|
||||
testTokensOf(t, c, tld, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
|
||||
testTokensOf(t, c, tld, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
|
||||
testTokensOf(t, c, tld, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still
|
||||
}
|
||||
|
||||
func testTokensOf(t *testing.T, c *neotest.ContractInvoker, result [][]byte, args ...interface{}) {
|
||||
func testTokensOf(t *testing.T, c *neotest.ContractInvoker, tld []byte, result [][]byte, args ...interface{}) {
|
||||
method := "tokensOf"
|
||||
if len(args) == 0 {
|
||||
method = "tokens"
|
||||
|
@ -399,31 +446,195 @@ func testTokensOf(t *testing.T, c *neotest.ContractInvoker, result [][]byte, arg
|
|||
require.Equal(t, result[i], iter.Value().Value())
|
||||
arr = append(arr, stackitem.Make(result[i]))
|
||||
}
|
||||
require.False(t, iter.Next())
|
||||
if method == "tokens" {
|
||||
require.True(t, iter.Next())
|
||||
require.Equal(t, tld, iter.Value().Value())
|
||||
} else {
|
||||
require.False(t, iter.Next())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
c := newNSClient(t, true)
|
||||
e := c.Executor
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
acc := e.NewAccount(t)
|
||||
cAcc := c.WithSigners(acc)
|
||||
cAccCommittee := c.WithSigners(acc, c.Committee)
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
cAcc.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")
|
||||
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
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, true, "register", "alias.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt")
|
||||
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
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, stackitem.Null{}, "resolve", "neo.com", int64(nns.AAAA))
|
||||
cAccCommittee.Invoke(t, true, "register", "alias2.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
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) {
|
||||
c := newNSClient(t, true)
|
||||
e := c.Executor
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
acc := e.NewAccount(t)
|
||||
cAcc := c.WithSigners(acc)
|
||||
cAccCommittee := c.WithSigners(acc, c.Committee)
|
||||
|
||||
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
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
|
||||
time := e.TopBlock(t).Timestamp
|
||||
|
||||
// Add some arbitrary data.
|
||||
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
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)
|
||||
h := e.InvokeScript(t, script, []neotest.Signer{acc})
|
||||
e.CheckHalt(t, h, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
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.SOA),
|
||||
stackitem.NewBuffer([]byte("neo.com" + " " + mail + " " +
|
||||
strconv.Itoa(int(time)) + " " + strconv.Itoa(int(refresh)) + " " +
|
||||
strconv.Itoa(int(retry)) + " " + strconv.Itoa(int(expire)) + " " +
|
||||
strconv.Itoa(int(ttl)))),
|
||||
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, true)
|
||||
e := c.Executor
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
acc := e.NewAccount(t)
|
||||
cAcc := c.WithSigners(acc)
|
||||
cAccCommittee := c.WithSigners(acc, c.Committee)
|
||||
|
||||
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||
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(), mail, refresh, retry, expire, ttl)
|
||||
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, true)
|
||||
cAccCommittee := c.WithSigners(c.Committee)
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
|
||||
cAccCommittee.Invoke(t, true, "register", "neo.com", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNNSRegisterArbitraryLevelDomain(t *testing.T) {
|
||||
c := newNSClient(t, true)
|
||||
|
||||
newArgs := func(domain string, account neotest.Signer) []interface{} {
|
||||
return []interface{}{
|
||||
domain, account.ScriptHash(), "doesnt@matter.com",
|
||||
int64(101), int64(102), int64(103), int64(104),
|
||||
}
|
||||
}
|
||||
acc := c.NewAccount(t)
|
||||
cBoth := c.WithSigners(c.Committee, acc)
|
||||
args := newArgs("neo.com", acc)
|
||||
cBoth.Invoke(t, true, "register", args...)
|
||||
|
||||
c1 := c.WithSigners(acc)
|
||||
// parent domain is missing
|
||||
args[0] = "testnet.fs.neo.com"
|
||||
c1.InvokeFail(t, "one of the parent domains is not registered", "register", args...)
|
||||
|
||||
args[0] = "fs.neo.com"
|
||||
c1.Invoke(t, true, "register", args...)
|
||||
|
||||
args[0] = "testnet.fs.neo.com"
|
||||
c1.Invoke(t, true, "register", args...)
|
||||
|
||||
acc2 := c.NewAccount(t)
|
||||
c2 := c.WithSigners(c.Committee, acc2)
|
||||
args = newArgs("mainnet.fs.neo.com", acc2)
|
||||
c2.InvokeFail(t, "not witnessed by admin", "register", args...)
|
||||
|
||||
c1.Invoke(t, stackitem.Null{}, "addRecord",
|
||||
"something.mainnet.fs.neo.com", int64(nns.A), "1.2.3.4")
|
||||
c1.Invoke(t, stackitem.Null{}, "addRecord",
|
||||
"another.fs.neo.com", int64(nns.A), "4.3.2.1")
|
||||
|
||||
c2 = c.WithSigners(acc, acc2)
|
||||
c2.Invoke(t, stackitem.NewBool(false), "isAvailable", "mainnet.fs.neo.com")
|
||||
c2.InvokeFail(t, "parent domain has conflicting records: something.mainnet.fs.neo.com",
|
||||
"register", args...)
|
||||
|
||||
c1.Invoke(t, stackitem.Null{}, "deleteRecords",
|
||||
"something.mainnet.fs.neo.com", int64(nns.A))
|
||||
c2.Invoke(t, stackitem.NewBool(true), "isAvailable", "mainnet.fs.neo.com")
|
||||
c2.Invoke(t, true, "register", args...)
|
||||
|
||||
c2 = c.WithSigners(acc2)
|
||||
c2.Invoke(t, stackitem.Null{}, "addRecord",
|
||||
"cdn.mainnet.fs.neo.com", int64(nns.A), "166.15.14.13")
|
||||
result := stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray([]byte("166.15.14.13")),
|
||||
})
|
||||
c2.Invoke(t, result, "resolve", "cdn.mainnet.fs.neo.com", int64(nns.A))
|
||||
}
|
||||
|
||||
const (
|
||||
defaultNameServiceDomainPrice = 10_0000_0000
|
||||
defaultNameServiceSysfee = 6000_0000
|
||||
maxRecordID = 255
|
||||
)
|
||||
|
|
|
@ -9,6 +9,8 @@ const (
|
|||
A RecordType = 1
|
||||
// CNAME represents canonical name record type.
|
||||
CNAME RecordType = 5
|
||||
// SOA represents start of authority record type.
|
||||
SOA RecordType = 6
|
||||
// TXT represents text record type.
|
||||
TXT RecordType = 16
|
||||
)
|
||||
|
|
|
@ -21,7 +21,10 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const neoAmount = 99999000
|
||||
const (
|
||||
neoAmount = 99999000
|
||||
millisecondsInYear = 365 * 24 * 3600 * 1000
|
||||
)
|
||||
|
||||
// Init pushes some predefined set of transactions into the given chain, it needs a path to
|
||||
// the root project directory.
|
||||
|
@ -158,17 +161,19 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) {
|
|||
_, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, nsConfigPath, 4) // block #11
|
||||
nsCommitteeInvoker := e.CommitteeInvoker(nsHash)
|
||||
nsPriv0Invoker := e.NewInvoker(nsHash, acc0)
|
||||
nsPriv0CommitteeInvoker := e.NewInvoker(nsHash, acc0, e.Committee)
|
||||
|
||||
// Block #12: transfer funds to committee for further NS record registration.
|
||||
gasValidatorInvoker.Invoke(t, true, "transfer",
|
||||
e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12
|
||||
|
||||
// Block #13: add `.com` root to NNS.
|
||||
nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13
|
||||
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||
nsCommitteeInvoker.Invoke(t, true, "register", "com", nsCommitteeInvoker.CommitteeHash, mail, refresh, retry, expire, ttl) // block #13
|
||||
|
||||
// Block #14: register `neo.com` via NNS.
|
||||
registerTxH := nsPriv0Invoker.Invoke(t, true, "register",
|
||||
"neo.com", priv0ScriptHash) // block #14
|
||||
registerTxH := nsPriv0CommitteeInvoker.Invoke(t, true, "register",
|
||||
"neo.com", priv0ScriptHash, mail, refresh, retry, expire, ttl) // block #14
|
||||
res := e.GetTxExecResult(t, registerTxH)
|
||||
require.Equal(t, 1, len(res.Events)) // transfer
|
||||
tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes()
|
||||
|
@ -176,7 +181,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
|
||||
|
|
|
@ -1370,7 +1370,7 @@ func TestClient_NEP11_ND(t *testing.T) {
|
|||
t.Run("TotalSupply", func(t *testing.T) {
|
||||
s, err := n11.TotalSupply()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, big.NewInt(1), s) // the only `neo.com` of acc0
|
||||
require.EqualValues(t, big.NewInt(2), s) // `neo.com` of acc0 and TLD `com` of committee
|
||||
})
|
||||
t.Run("Symbol", func(t *testing.T) {
|
||||
sym, err := n11.Symbol()
|
||||
|
@ -1403,14 +1403,14 @@ func TestClient_NEP11_ND(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
items, err := iter.Next(config.DefaultMaxIteratorResultItems)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(items))
|
||||
require.Equal(t, [][]byte{[]byte("neo.com")}, items)
|
||||
require.Equal(t, 2, len(items))
|
||||
require.Equal(t, [][]byte{[]byte("neo.com"), []byte("com")}, items)
|
||||
require.NoError(t, iter.Terminate())
|
||||
})
|
||||
t.Run("TokensExpanded", func(t *testing.T) {
|
||||
items, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [][]byte{[]byte("neo.com")}, items)
|
||||
require.Equal(t, [][]byte{[]byte("neo.com"), []byte("com")}, items)
|
||||
})
|
||||
t.Run("Properties", func(t *testing.T) {
|
||||
p, err := n11.Properties([]byte("neo.com"))
|
||||
|
@ -1421,6 +1421,7 @@ func TestClient_NEP11_ND(t *testing.T) {
|
|||
expected := stackitem.NewMap()
|
||||
expected.Add(stackitem.Make([]byte("name")), stackitem.Make([]byte("neo.com")))
|
||||
expected.Add(stackitem.Make([]byte("expiration")), stackitem.Make(blockRegisterDomain.Timestamp+365*24*3600*1000)) // expiration formula
|
||||
expected.Add(stackitem.Make([]byte("admin")), stackitem.Null{})
|
||||
require.EqualValues(t, expected, p)
|
||||
})
|
||||
t.Run("Transfer", func(t *testing.T) {
|
||||
|
|
|
@ -74,12 +74,12 @@ const (
|
|||
verifyContractHash = "06ed5314c2e4cb103029a60b86d46afa2fb8f67c"
|
||||
verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A="
|
||||
verifyWithArgsContractHash = "0dce75f52adb1a4c5c6eaa6a34eb26db2e5b3781"
|
||||
nnsContractHash = "bdbfe1a280a0e23ca5b569c8f5845169bd93cb06"
|
||||
nnsContractHash = "cb93bcab0d6d435b61fa96a3bbce3b6f043968b5"
|
||||
nnsToken1ID = "6e656f2e636f6d"
|
||||
nfsoContractHash = "0e15ca0df00669a2cd5dcb03bfd3e2b3849c2969"
|
||||
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
||||
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
||||
block20StateRootLE = "f1380226a217b5e35ea968d42c50e20b9af7ab83b91416c8fb85536c61004332"
|
||||
block20StateRootLE = "7f80c7e265a44faa7374953d4d5059d21b34e65e06a7695d57ca8c59cc9a36fa"
|
||||
storageContractHash = "ebc0c16a76c808cd4dde6bcc063f09e45e331ec7"
|
||||
)
|
||||
|
||||
|
@ -287,6 +287,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
return &map[string]interface{}{
|
||||
"name": "neo.com",
|
||||
"expiration": "lhbLRl0B",
|
||||
"admin": nil, // no admin was set
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -935,7 +936,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
chg := []dboper.Operation{{
|
||||
State: "Changed",
|
||||
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0xb},
|
||||
Value: []byte{0xf6, 0x8b, 0x4e, 0x9d, 0x51, 0x79, 0x12},
|
||||
Value: []byte{0x6e, 0xaf, 0xba, 0x5e, 0x51, 0x79, 0x12},
|
||||
}, {
|
||||
State: "Added",
|
||||
Key: []byte{0xfb, 0xff, 0xff, 0xff, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb},
|
||||
|
@ -947,7 +948,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
}, {
|
||||
State: "Changed",
|
||||
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0x14, 0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x8, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2},
|
||||
Value: []byte{0x41, 0x01, 0x21, 0x05, 0xe4, 0x74, 0xef, 0xdb, 0x08},
|
||||
Value: []byte{0x41, 0x01, 0x21, 0x05, 0xda, 0xb5, 0x8c, 0xda, 0x08},
|
||||
}}
|
||||
// Can be returned in any order.
|
||||
assert.ElementsMatch(t, chg, res.Diagnostics.Changes)
|
||||
|
@ -963,7 +964,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
||||
return &result.Invoke{
|
||||
State: "HALT",
|
||||
GasConsumed: 15928320,
|
||||
GasConsumed: 22192980,
|
||||
Script: script,
|
||||
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
||||
Notifications: []state.NotificationEvent{},
|
||||
|
@ -975,6 +976,15 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
{
|
||||
Current: nnsHash,
|
||||
Calls: []*invocations.Tree{
|
||||
{
|
||||
Current: stdHash,
|
||||
},
|
||||
{
|
||||
Current: cryptoHash,
|
||||
},
|
||||
{
|
||||
Current: stdHash,
|
||||
},
|
||||
{
|
||||
Current: stdHash,
|
||||
},
|
||||
|
@ -1078,7 +1088,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
||||
return &result.Invoke{
|
||||
State: "HALT",
|
||||
GasConsumed: 15928320,
|
||||
GasConsumed: 22192980,
|
||||
Script: script,
|
||||
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
||||
Notifications: []state.NotificationEvent{},
|
||||
|
@ -1090,6 +1100,15 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
{
|
||||
Current: nnsHash,
|
||||
Calls: []*invocations.Tree{
|
||||
{
|
||||
Current: stdHash,
|
||||
},
|
||||
{
|
||||
Current: cryptoHash,
|
||||
},
|
||||
{
|
||||
Current: stdHash,
|
||||
},
|
||||
{
|
||||
Current: stdHash,
|
||||
},
|
||||
|
@ -2717,7 +2736,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
|
|||
},
|
||||
{
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Amount: "37099660700",
|
||||
Amount: "37076412050",
|
||||
LastUpdated: 22,
|
||||
Decimals: 8,
|
||||
Name: "GasToken",
|
||||
|
|
BIN
pkg/services/rpcsrv/testdata/testblocks.acc
vendored
BIN
pkg/services/rpcsrv/testdata/testblocks.acc
vendored
Binary file not shown.
Loading…
Reference in a new issue