diff --git a/container/container_contract.go b/container/container_contract.go index 9c405f1..2f50e80 100644 --- a/container/container_contract.go +++ b/container/container_contract.go @@ -64,6 +64,12 @@ const ( // NotFoundError is returned if container is missing. NotFoundError = "container does not exist" + + // default SOA record field values + defaultRefresh = 3600 // 1 hour + defaultRetry = 600 // 10 min + defaultExpire = 604800 // 1 week + defaultTTL = 3600 // 1 hour ) var ( @@ -114,14 +120,18 @@ func _deploy(data interface{}, isUpdate bool) { } func registerNiceNameTLD(addrNNS interop.Hash160, nnsRoot string) { - iter := contract.Call(addrNNS, "roots", contract.ReadStates).(iterator.Iterator) - for iterator.Next(iter) { - if iterator.Value(iter).(string) == nnsRoot { - runtime.Log("NNS root is already registered: " + nnsRoot) - return - } + isAvail := contract.Call(addrNNS, "isAvailable", contract.AllowCall|contract.ReadStates, + "container").(bool) + if !isAvail { + return + } + + res := contract.Call(addrNNS, "register", contract.All, + nnsRoot, common.CommitteeAddress(), "ops@nspcc.ru", + defaultRefresh, defaultRetry, defaultExpire, defaultTTL).(bool) + if !res { + panic("can't register NNS TLD") } - contract.Call(addrNNS, "addRoot", contract.All, nnsRoot) } // Update method updates contract source code and manifest. Can be invoked @@ -237,12 +247,6 @@ func PutNamed(container []byte, signature interop.Signature, if name != "" { if needRegister { - const ( - defaultRefresh = 3600 // 1 hour - defaultRetry = 600 // 10 min - defaultExpire = 604800 // 1 week - defaultTTL = 3600 // 1 hour - ) res := contract.Call(nnsContractAddr, "register", contract.All, domain, runtime.GetExecutingScriptHash(), "ops@nspcc.ru", defaultRefresh, defaultRetry, defaultExpire, defaultTTL).(bool) diff --git a/nns/nns_contract.go b/nns/nns_contract.go index d815914..c8005fa 100644 --- a/nns/nns_contract.go +++ b/nns/nns_contract.go @@ -218,22 +218,6 @@ func Transfer(to interop.Hash160, tokenID []byte, data interface{}) bool { return true } -// AddRoot registers new root. -func AddRoot(root string) { - checkCommittee() - if !checkFragment(root, true) { - panic("invalid root format") - } - var ( - ctx = storage.GetContext() - rootKey = append([]byte{prefixRoot}, []byte(root)...) - ) - if storage.Get(ctx, rootKey) != nil { - panic("root already exists") - } - storage.Put(ctx, rootKey, 0) -} - // Roots returns iterator over a set of NameService roots. func Roots() iterator.Iterator { ctx := storage.GetReadOnlyContext() @@ -263,15 +247,36 @@ func IsAvailable(name string) bool { panic("invalid domain name format") } ctx := storage.GetReadOnlyContext() - if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil { - panic("root not found") - } - nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) - if nsBytes == nil { + l := len(fragments) + if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil { + if l != 1 { + panic("TLD not found") + } return true } - ns := std.Deserialize(nsBytes.([]byte)).(NameState) - return runtime.GetTime() >= ns.Expiration + return parentExpired(ctx, 0, fragments) +} + +// parentExpired returns true if any domain from fragments doesn't exist or expired. +// first denotes the deepest subdomain to check. +func parentExpired(ctx storage.Context, first int, fragments []string) bool { + now := runtime.GetTime() + last := len(fragments) - 1 + name := fragments[last] + for i := last; i >= first; i-- { + if i != last { + name = fragments[i] + "." + name + } + nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) + if nsBytes == nil { + return true + } + ns := std.Deserialize(nsBytes.([]byte)).(NameState) + if now >= ns.Expiration { + return true + } + } + return false } // Register registers new domain with the specified owner and name if it's available. @@ -280,9 +285,24 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry, if fragments == nil { panic("invalid domain name format") } + + l := len(fragments) + tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...) ctx := storage.GetContext() - if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil { - panic("root not found") + tldBytes := storage.Get(ctx, tldKey) + if l == 1 { + checkCommittee() + if tldBytes != nil { + 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 has expired") + } } if !isValid(owner) { @@ -701,9 +721,6 @@ func splitAndCheck(name string, allowMultipleFragments bool) []string { } fragments := std.StringSplit(name, ".") l = len(fragments) - if l < 2 { - return nil - } if l > 2 && !allowMultipleFragments { return nil } @@ -832,6 +849,9 @@ func tokenIDFromName(name string) string { panic("invalid domain name format") } l := len(fragments) + if l == 1 { + return name + } return name[len(name)-(len(fragments[l-1])+len(fragments[l-2])+1):] } diff --git a/tests/nns_test.go b/tests/nns_test.go index 2dff1b1..95c2b28 100644 --- a/tests/nns_test.go +++ b/tests/nns_test.go @@ -3,6 +3,7 @@ package tests import ( "fmt" "math/big" + "strings" "testing" "time" @@ -29,37 +30,48 @@ func TestNNSGeneric(t *testing.T) { CheckTestInvoke(t, bc, tx, 0) } -func TestNNSAddRoot(t *testing.T) { +func TestNNSRegisterTLD(t *testing.T) { bc := NewChain(t) h := DeployContract(t, bc, nnsPath, nil) - tx := PrepareInvoke(t, bc, CommitteeAcc, h, "addRoot", "0com") + refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) + tx := PrepareInvoke(t, bc, CommitteeAcc, h, "register", + "0com", CommitteeAcc.Contract.ScriptHash(), + "email@nspcc.ru", refresh, retry, expire, ttl) AddBlock(t, bc, tx) - CheckFault(t, bc, tx.Hash(), "invalid root format") + CheckFault(t, bc, tx.Hash(), "invalid domain name format") acc := NewAccount(t, bc) - tx = PrepareInvoke(t, bc, acc, h, "addRoot", "com") + tx = PrepareInvoke(t, bc, acc, h, "register", + "com", acc.Contract.ScriptHash(), + "email@nspcc.ru", refresh, retry, expire, ttl) AddBlock(t, bc, tx) CheckFault(t, bc, tx.Hash(), "not witnessed by committee") - tx = PrepareInvoke(t, bc, CommitteeAcc, h, "addRoot", "com") + tx = PrepareInvoke(t, bc, CommitteeAcc, h, "register", + "com", CommitteeAcc.Contract.ScriptHash(), + "email@nspcc.ru", refresh, retry, expire, ttl) AddBlock(t, bc, tx) CheckHalt(t, bc, tx.Hash()) - tx = PrepareInvoke(t, bc, CommitteeAcc, h, "addRoot", "com") + tx = PrepareInvoke(t, bc, CommitteeAcc, h, "register", + "com", CommitteeAcc.Contract.ScriptHash(), + "email@nspcc.ru", refresh, retry, expire, ttl) AddBlock(t, bc, tx) - CheckFault(t, bc, tx.Hash(), "root already exists") + CheckFault(t, bc, tx.Hash(), "TLD already exists") } func TestNNSRegister(t *testing.T) { bc := NewChain(t) h := DeployContract(t, bc, nnsPath, nil) - tx := PrepareInvoke(t, bc, CommitteeAcc, h, "addRoot", "com") + refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) + tx := PrepareInvoke(t, bc, CommitteeAcc, h, "register", + "com", CommitteeAcc.Contract.ScriptHash(), + "myemail@nspcc.ru", refresh, retry, expire, ttl) AddBlockCheckHalt(t, bc, tx) acc := NewAccount(t, bc) - refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) tx = PrepareInvoke(t, bc, []*wallet.Account{CommitteeAcc, acc}, h, "register", "testdomain.com", acc.Contract.ScriptHash(), "myemail@nspcc.ru", refresh, retry, expire, ttl) @@ -97,10 +109,12 @@ func TestNNSUpdateSOA(t *testing.T) { bc := NewChain(t) h := DeployContract(t, bc, nnsPath, nil) - tx := PrepareInvoke(t, bc, CommitteeAcc, h, "addRoot", "com") + refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) + tx := PrepareInvoke(t, bc, CommitteeAcc, h, "register", + "com", CommitteeAcc.Contract.ScriptHash(), + "myemail@nspcc.ru", refresh, retry, expire, ttl) AddBlockCheckHalt(t, bc, tx) - refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) tx = PrepareInvoke(t, bc, CommitteeAcc, h, "register", "testdomain.com", CommitteeAcc.Contract.ScriptHash(), "myemail@nspcc.ru", refresh, retry, expire, ttl) @@ -124,10 +138,12 @@ func TestNNSGetAllRecords(t *testing.T) { bc := NewChain(t) h := DeployContract(t, bc, nnsPath, nil) - tx := PrepareInvoke(t, bc, CommitteeAcc, h, "addRoot", "com") + refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) + tx := PrepareInvoke(t, bc, CommitteeAcc, h, "register", + "com", CommitteeAcc.Contract.ScriptHash(), + "myemail@nspcc.ru", refresh, retry, expire, ttl) AddBlockCheckHalt(t, bc, tx) - refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) tx = PrepareInvoke(t, bc, CommitteeAcc, h, "register", "testdomain.com", CommitteeAcc.Contract.ScriptHash(), "myemail@nspcc.ru", refresh, retry, expire, ttl) @@ -173,10 +189,12 @@ func TestNNSSetAdmin(t *testing.T) { bc := NewChain(t) h := DeployContract(t, bc, nnsPath, nil) - tx := PrepareInvoke(t, bc, CommitteeAcc, h, "addRoot", "com") + refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) + tx := PrepareInvoke(t, bc, CommitteeAcc, h, "register", + "com", CommitteeAcc.Contract.ScriptHash(), + "myemail@nspcc.ru", refresh, retry, expire, ttl) AddBlockCheckHalt(t, bc, tx) - refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) tx = PrepareInvoke(t, bc, CommitteeAcc, h, "register", "testdomain.com", CommitteeAcc.Contract.ScriptHash(), "myemail@nspcc.ru", refresh, retry, expire, ttl) @@ -198,15 +216,51 @@ func TestNNSSetAdmin(t *testing.T) { AddBlockCheckHalt(t, bc, tx) } +func TestNNSIsAvailable(t *testing.T) { + bc := NewChain(t) + h := DeployContract(t, bc, nnsPath, nil) + + tx := PrepareInvoke(t, bc, CommitteeAcc, h, "isAvailable", "com") + CheckTestInvoke(t, bc, tx, true) + + tx = PrepareInvoke(t, bc, CommitteeAcc, h, "isAvailable", "domain.com") + _, err := TestInvoke(bc, tx) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "TLD not found")) + + refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) + tx = PrepareInvoke(t, bc, CommitteeAcc, h, "register", + "com", CommitteeAcc.Contract.ScriptHash(), + "myemail@nspcc.ru", refresh, retry, expire, ttl) + AddBlockCheckHalt(t, bc, tx) + + tx = PrepareInvoke(t, bc, CommitteeAcc, h, "isAvailable", "com") + CheckTestInvoke(t, bc, tx, false) + + tx = PrepareInvoke(t, bc, CommitteeAcc, h, "isAvailable", "domain.com") + CheckTestInvoke(t, bc, tx, true) + + acc := NewAccount(t, bc) + tx = PrepareInvoke(t, bc, []*wallet.Account{CommitteeAcc, acc}, h, "register", + "domain.com", acc.Contract.ScriptHash(), + "myemail@nspcc.ru", refresh, retry, expire, ttl) + AddBlockCheckHalt(t, bc, tx) + + tx = PrepareInvoke(t, bc, CommitteeAcc, h, "isAvailable", "domain.com") + CheckTestInvoke(t, bc, tx, false) +} + func TestNNSRenew(t *testing.T) { bc := NewChain(t) h := DeployContract(t, bc, nnsPath, nil) - tx := PrepareInvoke(t, bc, CommitteeAcc, h, "addRoot", "com") + refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) + tx := PrepareInvoke(t, bc, CommitteeAcc, h, "register", + "com", CommitteeAcc.Contract.ScriptHash(), + "myemail@nspcc.ru", refresh, retry, expire, ttl) AddBlockCheckHalt(t, bc, tx) acc := NewAccount(t, bc) - refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) tx = PrepareInvoke(t, bc, []*wallet.Account{CommitteeAcc, acc}, h, "register", "testdomain.com", acc.Contract.ScriptHash(), "myemail@nspcc.ru", refresh, retry, expire, ttl)