[#115] nns: Allow 'Register' register TLD from nested domains
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
This commit is contained in:
parent
81853bd242
commit
2508ec4c17
2 changed files with 101 additions and 35 deletions
|
@ -44,6 +44,9 @@ const (
|
|||
//prefixGlobalDomain contains a flag indicating that this domain was created using GlobalDomain.
|
||||
//This is necessary to distinguish it from regular CNAME records.
|
||||
prefixGlobalDomain byte = 0x23
|
||||
// delInfo contains a list of counters that indicate the number of subdomains in the zone.
|
||||
// If it is null, a full scan of all domains must be performed.
|
||||
delInfo byte = 0x24
|
||||
)
|
||||
|
||||
// Values constraints.
|
||||
|
@ -86,6 +89,11 @@ type RecordState struct {
|
|||
ID byte
|
||||
}
|
||||
|
||||
type DelInfo struct {
|
||||
Count int
|
||||
IsAutoCreated bool
|
||||
}
|
||||
|
||||
// Update updates NameService contract.
|
||||
func Update(nef []byte, manifest string, data any) {
|
||||
checkCommittee()
|
||||
|
@ -234,13 +242,10 @@ func IsAvailable(name string) bool {
|
|||
ctx := storage.GetReadOnlyContext()
|
||||
l := len(fragments)
|
||||
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil {
|
||||
if l != 1 {
|
||||
panic("TLD not found")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
checkParentExists(ctx, fragments)
|
||||
checkParent(ctx, fragments)
|
||||
checkAvailableGlobalDomain(ctx, name)
|
||||
return storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) == nil
|
||||
}
|
||||
|
@ -304,33 +309,28 @@ func extractCnametgt(ctx storage.Context, name, domain string) string {
|
|||
return fragments[0] + "." + globalDomain
|
||||
}
|
||||
|
||||
// checkParentExists panics if any domain from fragments doesn't exist or is expired.
|
||||
func checkParentExists(ctx storage.Context, fragments []string) {
|
||||
if dom := parentExpired(ctx, fragments); dom != "" {
|
||||
panic("domain does not exist or is expired: " + dom)
|
||||
}
|
||||
}
|
||||
|
||||
// parentExpired returns domain from fragments that doesn't exist or is expired.
|
||||
// first denotes the deepest subdomain to check.
|
||||
func parentExpired(ctx storage.Context, fragments []string) string {
|
||||
// checkParent returns parent domain or empty string if domain not found.
|
||||
func checkParent(ctx storage.Context, fragments []string) string {
|
||||
now := int64(runtime.GetTime())
|
||||
last := len(fragments) - 1
|
||||
name := fragments[last]
|
||||
parent := ""
|
||||
for i := last; i > 0; i-- {
|
||||
if i != last {
|
||||
name = fragments[i] + "." + name
|
||||
}
|
||||
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...))
|
||||
if nsBytes == nil {
|
||||
return name
|
||||
continue
|
||||
}
|
||||
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
if now >= ns.Expiration {
|
||||
return name
|
||||
panic("domain expired: " + name)
|
||||
}
|
||||
parent = name
|
||||
}
|
||||
return ""
|
||||
return parent
|
||||
}
|
||||
|
||||
// Register registers a new domain with the specified owner and name if it's available.
|
||||
|
@ -345,6 +345,7 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
|
|||
l := len(fragments)
|
||||
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...)
|
||||
|
||||
prefixDelInfo := append([]byte{delInfo}, []byte(fragments[len(fragments)-1])...)
|
||||
tldBytes := storage.Get(ctx, tldKey)
|
||||
if l == 1 {
|
||||
checkCommittee()
|
||||
|
@ -353,13 +354,22 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
|
|||
}
|
||||
storage.Put(ctx, tldKey, 0)
|
||||
} else {
|
||||
if tldBytes == nil {
|
||||
panic("TLD not found")
|
||||
}
|
||||
checkParentExists(ctx, fragments)
|
||||
parent := checkParent(ctx, fragments)
|
||||
if parent == "" {
|
||||
parent = fragments[len(fragments)-1]
|
||||
|
||||
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
|
||||
var delInfo DelInfo
|
||||
delInfo.IsAutoCreated = true
|
||||
storage.Put(ctx, prefixDelInfo, std.Serialize(delInfo))
|
||||
|
||||
register(ctx, parent, owner, email, refresh, retry, expire, ttl)
|
||||
}
|
||||
|
||||
parentKey := getTokenKey([]byte(parent))
|
||||
nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...))
|
||||
if nsBytes == nil {
|
||||
panic("parent does not exist:" + parent)
|
||||
}
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
ns.checkAdmin()
|
||||
|
||||
|
@ -408,6 +418,14 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
|
|||
updateBalance(ctx, []byte(name), owner, +1)
|
||||
postTransfer(oldOwner, owner, []byte(name), nil)
|
||||
runtime.Notify("RegisterDomain", name)
|
||||
|
||||
var delInfo DelInfo
|
||||
delInfoRaw := storage.Get(ctx, prefixDelInfo)
|
||||
if delInfoRaw != nil {
|
||||
delInfo = std.Deserialize(delInfoRaw.([]byte)).(DelInfo)
|
||||
}
|
||||
delInfo.Count = delInfo.Count + 1
|
||||
storage.Put(ctx, prefixDelInfo, std.Serialize(delInfo))
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -577,14 +595,33 @@ func DeleteDomain(name string) {
|
|||
deleteDomain(ctx, name)
|
||||
}
|
||||
|
||||
func deleteDomain(ctx storage.Context, name string) {
|
||||
func restoreCountSubDomain(ctx storage.Context, name string) int {
|
||||
countSubDomain := 0
|
||||
it := Tokens()
|
||||
for iterator.Next(it) {
|
||||
domain := iterator.Value(it)
|
||||
if std.MemorySearch([]byte(domain.(string)), []byte(name)) > 0 {
|
||||
panic("can't delete a domain that has subdomains")
|
||||
countSubDomain = countSubDomain + 1
|
||||
}
|
||||
}
|
||||
return countSubDomain
|
||||
}
|
||||
|
||||
func deleteDomain(ctx storage.Context, name string) {
|
||||
fragments := splitAndCheck(name)
|
||||
zoneCounterToken := append([]byte{delInfo}, []byte(fragments[len(fragments)-1])...)
|
||||
var delInfo DelInfo
|
||||
delInfoRaw := storage.Get(ctx, zoneCounterToken)
|
||||
if delInfoRaw != nil {
|
||||
delInfo = std.Deserialize(delInfoRaw.([]byte)).(DelInfo)
|
||||
} else {
|
||||
delInfo.Count = restoreCountSubDomain(ctx, fragments[len(fragments)-1])
|
||||
storage.Put(ctx, zoneCounterToken, std.Serialize(delInfo))
|
||||
}
|
||||
|
||||
if delInfo.Count > 1 && len(fragments) == 1 {
|
||||
panic("can't delete TLD domain that has subdomains")
|
||||
}
|
||||
|
||||
nsKey := append([]byte{prefixName}, getTokenKey([]byte(name))...)
|
||||
nsRaw := storage.Get(ctx, nsKey)
|
||||
|
@ -608,6 +645,17 @@ func deleteDomain(ctx storage.Context, name string) {
|
|||
deleteRecords(ctx, name, AAAA)
|
||||
storage.Delete(ctx, nsKey)
|
||||
storage.Delete(ctx, append([]byte{prefixRoot}, []byte(name)...))
|
||||
delInfo.Count = delInfo.Count - 1
|
||||
storage.Put(ctx, zoneCounterToken, std.Serialize(delInfo))
|
||||
|
||||
if delInfo.Count == 1 && delInfo.IsAutoCreated {
|
||||
deleteDomain(ctx, fragments[len(fragments)-1])
|
||||
}
|
||||
|
||||
if len(fragments) == 1 {
|
||||
storage.Delete(ctx, zoneCounterToken)
|
||||
}
|
||||
|
||||
runtime.Notify("DeleteDomain", name)
|
||||
}
|
||||
|
||||
|
@ -681,7 +729,7 @@ func getNameState(ctx storage.Context, tokenID []byte) NameState {
|
|||
tokenKey := getTokenKey(tokenID)
|
||||
ns := getNameStateWithKey(ctx, tokenKey)
|
||||
fragments := std.StringSplit(string(tokenID), ".")
|
||||
checkParentExists(ctx, fragments)
|
||||
checkParent(ctx, fragments)
|
||||
return ns
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,18 @@ func TestNNSRegister(t *testing.T) {
|
|||
"com", accTop.ScriptHash(),
|
||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||
|
||||
c1.Invoke(t, true, "register",
|
||||
"aa.bb.zz", accTop.ScriptHash(),
|
||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||
|
||||
c1.InvokeFail(t, "TLD already exists", "register",
|
||||
"zz", accTop.ScriptHash(),
|
||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||
|
||||
c1.Invoke(t, true, "register",
|
||||
"xx.bb.zz", accTop.ScriptHash(),
|
||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||
|
||||
acc := c.NewAccount(t)
|
||||
c2 := c.WithSigners(c.Committee, acc)
|
||||
c2.InvokeFail(t, "not witnessed by admin", "register",
|
||||
|
@ -251,10 +263,23 @@ func TestDeleteDomain(t *testing.T) {
|
|||
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
|
||||
|
||||
c1.InvokeFail(t, "domain not found", "deleteDomain", "ru")
|
||||
c1.InvokeFail(t, "can't delete a domain that has subdomains", "deleteDomain", "testdomain.com")
|
||||
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "domik.testdomain.com")
|
||||
c1.InvokeFail(t, "can't delete TLD domain that has subdomains", "deleteDomain", "com")
|
||||
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com")
|
||||
|
||||
c1.Invoke(t, true, "register",
|
||||
"aa.bb.zzq", acc1.ScriptHash(),
|
||||
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
|
||||
|
||||
c1.InvokeFail(t, "TLD already exists", "register",
|
||||
"zzq", acc1.ScriptHash(),
|
||||
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
|
||||
|
||||
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "aa.bb.zzq")
|
||||
|
||||
c1.Invoke(t, true, "register",
|
||||
"zzq", acc1.ScriptHash(),
|
||||
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
|
||||
|
||||
c1.Invoke(t, true, "register",
|
||||
"cn", acc1.ScriptHash(),
|
||||
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
|
||||
|
@ -335,11 +360,6 @@ func TestNNSRegisterMulti(t *testing.T) {
|
|||
cBoth.Invoke(t, true, "register", args...)
|
||||
|
||||
c1 := c.WithSigners(acc)
|
||||
t.Run("parent domain is missing", func(t *testing.T) {
|
||||
msg := "domain does not exist or is expired: fs.neo.com"
|
||||
args[0] = "testnet.fs.neo.com"
|
||||
c1.InvokeFail(t, msg, "register", args...)
|
||||
})
|
||||
|
||||
args[0] = "fs.neo.com"
|
||||
c1.Invoke(t, true, "register", args...)
|
||||
|
@ -499,7 +519,7 @@ func TestNNSIsAvailable(t *testing.T) {
|
|||
c := newNNSInvoker(t, false)
|
||||
|
||||
c.Invoke(t, true, "isAvailable", "com")
|
||||
c.InvokeFail(t, "TLD not found", "isAvailable", "domain.com")
|
||||
//c.InvokeFail(t, "TLD not found", "isAvailable", "domain.com")
|
||||
|
||||
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
|
||||
c.Invoke(t, true, "register",
|
||||
|
@ -512,7 +532,6 @@ func TestNNSIsAvailable(t *testing.T) {
|
|||
acc := c.NewAccount(t)
|
||||
c1 := c.WithSigners(c.Committee, acc)
|
||||
|
||||
c1.InvokeFail(t, "domain does not exist or is expired: domain.com", "isAvailable", "dom.domain.com")
|
||||
c1.Invoke(t, true, "register",
|
||||
"domain.com", acc.ScriptHash(),
|
||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||
|
@ -524,7 +543,6 @@ func TestNNSIsAvailable(t *testing.T) {
|
|||
c.Invoke(t, false, "isAvailable", "domain.com")
|
||||
|
||||
c.Invoke(t, true, "isAvailable", "dom.domain.com")
|
||||
c.InvokeFail(t, "domain does not exist or is expired: dom.domain.com", "isAvailable", "dom.dom.domain.com")
|
||||
|
||||
c1.Invoke(t, true, "register",
|
||||
"dom.domain.com", acc.ScriptHash(),
|
||||
|
|
Loading…
Reference in a new issue