[#56] Add multi-level domain name support
All checks were successful
DCO action / DCO (pull_request) Successful in 1m0s
Tests / Tests (1.20) (pull_request) Successful in 1m29s
Tests / Tests (1.19) (pull_request) Successful in 1m35s

Signed-off-by: Ekaterina Lebedeva <ekaterina.lebedeva@yadro.com>
This commit is contained in:
Ekaterina Lebedeva 2023-12-12 20:22:33 +03:00
parent 897f538a3c
commit 4dcb575caa
2 changed files with 22 additions and 13 deletions

View file

@ -219,7 +219,7 @@ func GetPrice() int {
// IsAvailable checks whether the provided domain name is available. // IsAvailable checks whether the provided domain name is available.
func IsAvailable(name string) bool { func IsAvailable(name string) bool {
fragments := splitAndCheck(name, false) fragments := splitAndCheck(name)
if fragments == nil { if fragments == nil {
panic("invalid domain name format") panic("invalid domain name format")
} }
@ -231,16 +231,19 @@ func IsAvailable(name string) bool {
} }
return true return true
} }
return parentExpired(ctx, 0, fragments) if parentExpired(ctx, fragments) {
panic("parent does not exist or is expired")
}
return storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) == nil
} }
// parentExpired returns true if any domain from fragments doesn't exist or is expired. // parentExpired returns true if any domain from fragments doesn't exist or is expired.
// first denotes the deepest subdomain to check. // first denotes the deepest subdomain to check.
func parentExpired(ctx storage.Context, first int, fragments []string) bool { func parentExpired(ctx storage.Context, fragments []string) bool {
now := int64(runtime.GetTime()) now := int64(runtime.GetTime())
last := len(fragments) - 1 last := len(fragments) - 1
name := fragments[last] name := fragments[last]
for i := last; i >= first; i-- { for i := last; i > 0; i-- {
if i != last { if i != last {
name = fragments[i] + "." + name name = fragments[i] + "." + name
} }
@ -258,7 +261,7 @@ func parentExpired(ctx storage.Context, first int, fragments []string) bool {
// Register registers a new domain with the specified owner and name if it's available. // Register registers a new domain with the specified owner and name if it's available.
func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool { func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
fragments := splitAndCheck(name, true) fragments := splitAndCheck(name)
if fragments == nil { if fragments == nil {
panic("invalid domain name format") panic("invalid domain name format")
} }
@ -277,7 +280,7 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
if tldBytes == nil { if tldBytes == nil {
panic("TLD not found") panic("TLD not found")
} }
if parentExpired(ctx, 1, fragments) { if parentExpired(ctx, fragments) {
panic("one of the parent domains is not registered") panic("one of the parent domains is not registered")
} }
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:])) parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
@ -388,7 +391,7 @@ func checkBaseRecords(typ RecordType, data string) bool {
case A: case A:
return checkIPv4(data) return checkIPv4(data)
case CNAME: case CNAME:
return splitAndCheck(data, true) != nil return splitAndCheck(data) != nil
case TXT: case TXT:
return len(data) <= maxTXTRecordLength return len(data) <= maxTXTRecordLength
case AAAA: case AAAA:
@ -508,7 +511,7 @@ func getNameState(ctx storage.Context, tokenID []byte) NameState {
tokenKey := getTokenKey(tokenID) tokenKey := getTokenKey(tokenID)
ns := getNameStateWithKey(ctx, tokenKey) ns := getNameStateWithKey(ctx, tokenKey)
fragments := std.StringSplit(string(tokenID), ".") fragments := std.StringSplit(string(tokenID), ".")
if parentExpired(ctx, 1, fragments) { if parentExpired(ctx, fragments) {
panic("parent domain has expired") panic("parent domain has expired")
} }
return ns return ns
@ -716,16 +719,13 @@ func isAlNum(c uint8) bool {
} }
// splitAndCheck splits domain name into parts and validates it. // splitAndCheck splits domain name into parts and validates it.
func splitAndCheck(name string, allowMultipleFragments bool) []string { func splitAndCheck(name string) []string {
l := len(name) l := len(name)
if l < minDomainNameLength || maxDomainNameLength < l { if l < minDomainNameLength || maxDomainNameLength < l {
return nil return nil
} }
fragments := std.StringSplit(name, ".") fragments := std.StringSplit(name, ".")
l = len(fragments) l = len(fragments)
if l > 2 && !allowMultipleFragments {
return nil
}
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
if !checkFragment(fragments[i], i == l-1) { if !checkFragment(fragments[i], i == l-1) {
return nil return nil
@ -846,7 +846,7 @@ func checkIPv6(data string) bool {
// tokenIDFromName returns token ID (domain.root) from the provided name. // tokenIDFromName returns token ID (domain.root) from the provided name.
func tokenIDFromName(name string) string { func tokenIDFromName(name string) string {
fragments := splitAndCheck(name, true) fragments := splitAndCheck(name)
if fragments == nil { if fragments == nil {
panic("invalid domain name format") panic("invalid domain name format")
} }

View file

@ -334,6 +334,15 @@ func TestNNSIsAvailable(t *testing.T) {
"myemail@frostfs.info", refresh, retry, expire, ttl) "myemail@frostfs.info", refresh, retry, expire, ttl)
c.Invoke(t, false, "isAvailable", "domain.com") c.Invoke(t, false, "isAvailable", "domain.com")
c.Invoke(t, true, "isAvailable", "dom.domain.com")
c.InvokeFail(t, "parent does not exist or is expired", "isAvailable", "dom.dom.domain.com")
c1.Invoke(t, true, "register",
"dom.domain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c.Invoke(t, false, "isAvailable", "dom.domain.com")
c.Invoke(t, true, "isAvailable", "dom.dom.domain.com")
} }
func TestNNSRenew(t *testing.T) { func TestNNSRenew(t *testing.T) {