Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nns: Register pre-defined TLDs for the committee on deployment stage #344

Merged
Merged
5 changes: 5 additions & 0 deletions nns/namestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

// NameState represents domain name state.
type NameState struct {
// Domain name owner. Nil if owned by the committee.
Owner interop.Hash160
Name string
Expiration int64
Expand All @@ -22,6 +23,10 @@ func (n NameState) ensureNotExpired() {

// checkAdmin panics if script container is not signed by the domain name admin.
func (n NameState) checkAdmin() {
if len(n.Owner) == 0 {
checkCommittee()
return
}
if runtime.CheckWitness(n.Owner) {
return
}
Expand Down
50 changes: 33 additions & 17 deletions nns/nns_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ 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.
//
// Access rules::
// - TLD can be registered only by the committee
// - 2nd-level domain can be registered by anyone
// - starting from the 3rd level, the domain can only be registered by the
// owner or administrator (if any) of the previous level domain
AnnaShaleva marked this conversation as resolved.
Show resolved Hide resolved
func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
fragments := splitAndCheck(name, true)
if fragments == nil {
Expand All @@ -276,27 +282,37 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
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:]))
putNameStateWithKey(ctx, getTokenKey([]byte(name)), NameState{
Name: name,
// NNS expiration is in milliseconds
Expiration: int64(runtime.GetTime() + expire*1000),
})
AnnaShaleva marked this conversation as resolved.
Show resolved Hide resolved
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
return true
AnnaShaleva marked this conversation as resolved.
Show resolved Hide resolved
}

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:]))

if l > 2 {
nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...))
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
ns.checkAdmin()
}

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) {
panic("parent domain has conflicting records: " + r.Name)
}
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) {
panic("parent domain has conflicting records: " + r.Name)
}
}

Expand Down
57 changes: 53 additions & 4 deletions tests/nns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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/vm/stackitem"
"github.com/nspcc-dev/neofs-contract/common"
"github.com/nspcc-dev/neofs-contract/nns"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -78,10 +79,6 @@ func TestNNSRegister(t *testing.T) {
"myemail@nspcc.ru", refresh, retry, expire, ttl)

acc := c.NewAccount(t)
c2 := c.WithSigners(c.Committee, acc)
c2.InvokeFail(t, "not witnessed by admin", "register",
"testdomain.com", acc.ScriptHash(),
"myemail@nspcc.ru", refresh, retry, expire, ttl)

c3 := c.WithSigners(accTop, acc)
t.Run("domain names with hyphen", func(t *testing.T) {
Expand Down Expand Up @@ -371,3 +368,55 @@ func TestNNSResolve(t *testing.T) {
c.Invoke(t, records, "resolve", "test.com.", int64(nns.TXT))
c.InvokeFail(t, "invalid domain name format", "resolve", "test.com..", int64(nns.TXT))
}

func TestNNSRegisterAccess(t *testing.T) {
inv := newNNSInvoker(t, false)
const email, refresh, retry, expire, ttl = "user@domain.org", 0, 1, 2, 3
const tld = "com"
const registerMethod = "register"
const ownerWitnessFailMsg = "not witnessed by committee"

// TLD
l2OwnerAcc := inv.NewAccount(t)
l2OwnerInv := inv.WithSigners(l2OwnerAcc)

l2OwnerInv.InvokeFail(t, ownerWitnessFailMsg, registerMethod,
tld, l2OwnerAcc.ScriptHash(), email, refresh, retry, expire, ttl)
l2OwnerInv.InvokeFail(t, ownerWitnessFailMsg, registerMethod,
tld, nil, email, refresh, retry, expire, ttl)

inv.WithSigners(inv.Committee).Invoke(t, true, registerMethod,
tld, nil, email, refresh, retry, expire, ttl)

// L2
const l2 = "l2." + tld

anonymousAcc := inv.NewAccount(t)
anonymousInv := inv.WithSigners(anonymousAcc)

l2OwnerInv.InvokeFail(t, "invalid owner", registerMethod,
l2, nil, email, refresh, retry, expire, ttl)
l2OwnerInv.InvokeFail(t, common.ErrOwnerWitnessFailed, registerMethod,
l2, anonymousAcc.ScriptHash(), email, refresh, retry, expire, ttl)
l2OwnerInv.Invoke(t, true, registerMethod,
l2, l2OwnerAcc.ScriptHash(), email, refresh, retry, expire, ttl)

// L3 (by L2 owner)
const l3ByL2Owner = "l3-owner." + l2

l2OwnerInv.Invoke(t, true, registerMethod,
l3ByL2Owner, l2OwnerAcc.ScriptHash(), email, refresh, retry, expire, ttl)

// L3 (by L2 admin)
const l3ByL2Admin = "l3-admin." + l2

l2AdminAcc := inv.NewAccount(t)
l2AdminInv := inv.WithSigners(l2AdminAcc)

inv.WithSigners(l2OwnerAcc, l2AdminAcc).Invoke(t, stackitem.Null{}, "setAdmin", l2, l2AdminAcc.ScriptHash())

anonymousInv.InvokeFail(t, "not witnessed by admin", registerMethod,
l3ByL2Admin, anonymousAcc.ScriptHash(), email, refresh, retry, expire, ttl)
l2AdminInv.Invoke(t, true, "register",
l3ByL2Admin, l2AdminAcc.ScriptHash(), email, refresh, retry, expire, ttl)
}