nns: allow arbitrary level domains

Port
d10da892d9.
This commit is contained in:
Anna Shaleva 2022-09-15 22:27:07 +03:00
parent ea934b8e30
commit db9cea5ecc
2 changed files with 71 additions and 15 deletions

View file

@ -247,7 +247,7 @@ func parentExpired(ctx storage.Context, first int, fragments []string) bool {
// Register registers 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 {
fragments := splitAndCheck(name, false)
fragments := splitAndCheck(name, true)
if fragments == nil {
panic("invalid domain name format")
}
@ -266,9 +266,9 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
panic("TLD not found")
}
if parentExpired(ctx, 1, fragments) {
panic("one of the parent domains has expired")
panic("one of the parent domains is not registered")
}
parentKey := getTokenKey([]byte(fragments[1]))
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...))
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
ns.checkAdmin()
@ -331,7 +331,7 @@ func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expir
std.Itoa(retry, 10) + " " +
std.Itoa(expire, 10) + " " +
std.Itoa(ttl, 10)
tokenId := []byte(tokenIDFromName(name))
tokenId := []byte(tokenIDFromName(ctx, name))
putRecord(ctx, tokenId, name, SOA, 0, data)
}
@ -428,7 +428,7 @@ func AddRecord(name string, typ RecordType, data string) {
// checkRecord performs record validness check and returns token ID.
func checkRecord(ctx storage.Context, name string, typ RecordType, data string) []byte {
tokenID := []byte(tokenIDFromName(name))
tokenID := []byte(tokenIDFromName(ctx, name))
var ok bool
switch typ {
case A:
@ -453,8 +453,8 @@ func checkRecord(ctx storage.Context, name string, typ RecordType, data string)
// GetRecords returns domain records of the specified type if they exist or an empty
// array if not.
func GetRecords(name string, typ RecordType) []string {
tokenID := []byte(tokenIDFromName(name))
ctx := storage.GetReadOnlyContext()
tokenID := []byte(tokenIDFromName(ctx, name))
_ = getNameState(ctx, tokenID) // ensure not expired
return getRecordsByType(ctx, tokenID, name, typ)
}
@ -464,8 +464,8 @@ func DeleteRecords(name string, typ RecordType) {
if typ == SOA {
panic("forbidden to delete SOA record")
}
tokenID := []byte(tokenIDFromName(name))
ctx := storage.GetContext()
tokenID := []byte(tokenIDFromName(ctx, name))
ns := getNameState(ctx, tokenID)
ns.checkAdmin()
recordsPrefix := getRecordsByTypePrefix(tokenID, name, typ)
@ -554,7 +554,7 @@ func getNameState(ctx storage.Context, tokenID []byte) NameState {
// 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")
@ -564,6 +564,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))
@ -806,16 +811,25 @@ 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)
if l == 1 {
return name
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:]
}
return name[len(name)-(len(fragments[l-1])+len(fragments[l-2])+1):]
}
sum += len(fragments[i]) + 1
}
return name
}
// resolve resolves provided name using record with the specified type and given
@ -855,7 +869,7 @@ func resolve(ctx storage.Context, res []string, name string, typ RecordType, red
// specified name. Records returned are of different types and/or different IDs.
// No keys are returned.
func getAllRecords(ctx storage.Context, name string) iterator.Iterator {
tokenID := []byte(tokenIDFromName(name))
tokenID := []byte(tokenIDFromName(ctx, name))
_ = getNameState(ctx, tokenID) // ensure not expired.
recordsPrefix := getRecordsPrefix(tokenID, name)
return storage.Find(ctx, recordsPrefix, storage.ValuesOnly|storage.DeserializeValues)

View file

@ -168,7 +168,7 @@ func TestRegisterAndRenew(t *testing.T) {
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, mail, refresh, retry, expire, ttl)
c.InvokeFail(t, "invalid domain name format", "register", "docs.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)
@ -593,6 +593,48 @@ func TestNNSAddRecord(t *testing.T) {
}
}
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...)
c2 = c.WithSigners(acc, acc2)
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