[#115] nns: Allow 'Register' register TLD from nested domains
Some checks failed
Code generation / Generate wrappers (pull_request) Successful in 54s
DCO action / DCO (pull_request) Successful in 58s
Tests / Tests (pull_request) Failing after 1m28s

Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
This commit is contained in:
Alexander Chuprov 2024-09-30 22:07:41 +03:00
parent 81853bd242
commit d449e0adf1
2 changed files with 67 additions and 31 deletions

View file

@ -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
// prefixZoneCounterSubdomain 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.
prefixZoneCounterSubdomain byte = 0x24
)
// Values constraints.
@ -240,7 +243,7 @@ func IsAvailable(name string) bool {
return true
}
checkParentExists(ctx, fragments)
checkParent(ctx, fragments)
checkAvailableGlobalDomain(ctx, name)
return storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) == nil
}
@ -304,33 +307,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.
@ -352,14 +350,19 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
panic("TLD already exists")
}
storage.Put(ctx, tldKey, 0)
storage.Put(ctx, append([]byte{prefixZoneCounterSubdomain}, []byte(fragments[l-1])...), 0)
} else {
if tldBytes == nil {
panic("TLD not found")
parent := checkParent(ctx, fragments)
if parent == "" {
parent = fragments[len(fragments)-1]
register(ctx, parent, owner, email, refresh, retry, expire, ttl)
}
checkParentExists(ctx, fragments)
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
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 +411,15 @@ 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)
if l != 1 {
zoneCounterKey := []byte{prefixZoneCounterSubdomain}
data := storage.Get(ctx, zoneCounterKey)
if data != nil {
storage.Put(ctx, zoneCounterKey, data.(int)+1)
}
}
return true
}
@ -577,12 +589,34 @@ func DeleteDomain(name string) {
deleteDomain(ctx, name)
}
func deleteDomain(ctx storage.Context, name string) {
func restoreCountSubDomain(ctx storage.Context, name string, token []byte) 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
}
}
storage.Put(ctx, token, countSubDomain)
return countSubDomain
}
func deleteDomain(ctx storage.Context, name string) {
fragments := splitAndCheck(name)
zoneCounterToken := []byte{prefixZoneCounterSubdomain}
countSubDomains := 0
if len(fragments) == 1 {
countRaw := storage.Get(ctx, zoneCounterToken)
if countRaw != nil {
countSubDomains = countRaw.(int)
} else {
countSubDomains = restoreCountSubDomain(ctx, name, zoneCounterToken)
}
if countSubDomains > 0 {
panic("can't delete TLD domain that has subdomains")
}
}
@ -608,6 +642,11 @@ func deleteDomain(ctx storage.Context, name string) {
deleteRecords(ctx, name, AAAA)
storage.Delete(ctx, nsKey)
storage.Delete(ctx, append([]byte{prefixRoot}, []byte(name)...))
storage.Put(ctx, zoneCounterToken, countSubDomains-1)
if len(fragments) == 1 {
storage.Delete(ctx, zoneCounterToken)
}
runtime.Notify("DeleteDomain", name)
}
@ -681,7 +720,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
}

View file

@ -100,6 +100,10 @@ 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)
acc := c.NewAccount(t)
c2 := c.WithSigners(c.Committee, acc)
c2.InvokeFail(t, "not witnessed by admin", "register",
@ -251,9 +255,10 @@ 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, stackitem.Null{}, "deleteDomain", "domik.testdomain.com")
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "com")
c1.Invoke(t, true, "register",
"cn", acc1.ScriptHash(),
@ -335,12 +340,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...)
@ -512,7 +511,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 +522,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(),