nns: replace root with TLD

Port
4b86891d57.
This commit is contained in:
Anna Shaleva 2022-09-05 17:30:47 +03:00
parent c11481b119
commit 5cb2a1219c
3 changed files with 88 additions and 59 deletions

View file

@ -179,22 +179,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()
@ -224,15 +208,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")
l := len(fragments)
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil {
if l != 1 {
panic("TLD not found")
}
return true
}
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)
return runtime.GetTime() >= ns.Expiration
if now >= ns.Expiration {
return true
}
}
return false
}
// Register registers new domain with the specified owner and name if it's available.
@ -241,9 +246,23 @@ func Register(name string, owner interop.Hash160) bool {
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) {
@ -548,9 +567,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
}
@ -679,6 +695,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):]
}

View file

@ -70,21 +70,21 @@ func TestNonfungible(t *testing.T) {
c.Invoke(t, 0, "totalSupply")
}
func TestAddRoot(t *testing.T) {
func TestRegisterTLD(t *testing.T) {
c := newNSClient(t)
t.Run("invalid format", func(t *testing.T) {
c.InvokeFail(t, "invalid root format", "addRoot", "")
c.InvokeFail(t, "invalid domain name format", "register", "", c.CommitteeHash)
})
t.Run("not signed by committee", func(t *testing.T) {
acc := c.NewAccount(t)
c := c.WithSigners(acc)
c.InvokeFail(t, "not witnessed by committee", "addRoot", "some")
c.InvokeFail(t, "not witnessed by committee", "register", "some", c.CommitteeHash)
})
c.Invoke(t, stackitem.Null{}, "addRoot", "some")
c.Invoke(t, true, "register", "some", c.CommitteeHash)
t.Run("already exists", func(t *testing.T) {
c.InvokeFail(t, "already exists", "addRoot", "some")
c.InvokeFail(t, "TLD already exists", "register", "some", c.CommitteeHash)
})
}
@ -96,7 +96,7 @@ func TestExpiration(t *testing.T) {
acc := e.NewAccount(t)
cAcc := c.WithSigners(acc)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
b1 := e.TopBlock(t)
@ -107,7 +107,7 @@ func TestExpiration(t *testing.T) {
b2.PrevHash = b1.Hash()
b2.Timestamp = b1.Timestamp + 10000
require.NoError(t, bc.AddBlock(e.SignBlock(b2)))
e.CheckHalt(t, tx.Hash())
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true))
tx = cAcc.PrepareInvoke(t, "isAvailable", "first.com")
b3 := e.NewUnsignedBlock(t, tx)
@ -115,7 +115,7 @@ func TestExpiration(t *testing.T) {
b3.PrevHash = b2.Hash()
b3.Timestamp = b1.Timestamp + (millisecondsInYear + 1)
require.NoError(t, bc.AddBlock(e.SignBlock(b3)))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true)) // "first.com" has been expired
tx = cAcc.PrepareInvoke(t, "isAvailable", "second.com")
b4 := e.NewUnsignedBlock(t, tx)
@ -123,7 +123,7 @@ func TestExpiration(t *testing.T) {
b4.PrevHash = b3.Hash()
b4.Timestamp = b3.Timestamp + 1000
require.NoError(t, bc.AddBlock(e.SignBlock(b4)))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(false))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true)) // TLD "com" has been expired
tx = cAcc.PrepareInvoke(t, "getRecord", "first.com", int64(nns.TXT))
b5 := e.NewUnsignedBlock(t, tx)
@ -133,8 +133,12 @@ func TestExpiration(t *testing.T) {
require.NoError(t, bc.AddBlock(e.SignBlock(b5)))
e.CheckFault(t, tx.Hash(), "name has expired")
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash()) // Re-register.
cAcc.Invoke(t, stackitem.Null{}, "resolve", "first.com", int64(nns.TXT))
// TODO: According to the new code, we can't re-register expired "com" TLD, because it's already registered; at the
// same time we can't renew it because it's already expired. We likely need to change this logic in the contract and
// after that uncomment the lines below.
// c.Invoke(t, true, "renew", "com")
// cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash()) // Re-register.
// cAcc.Invoke(t, stackitem.Null{}, "resolve", "first.com", int64(nns.TXT))
}
const (
@ -146,10 +150,10 @@ func TestRegisterAndRenew(t *testing.T) {
c := newNSClient(t)
e := c.Executor
c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
c.Invoke(t, stackitem.Null{}, "addRoot", "org")
c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
c.Invoke(t, true, "register", "org", c.CommitteeHash)
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
c.Invoke(t, true, "isAvailable", "neo.com")
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash)
c.InvokeFail(t, "invalid domain name format", "register", "docs.neo.org", e.CommitteeHash)
@ -166,7 +170,7 @@ func TestRegisterAndRenew(t *testing.T) {
c.InvokeFail(t, "invalid domain name format", "register", maxLenFragment+"q.com", e.CommitteeHash)
c.Invoke(t, true, "isAvailable", "neo.com")
c.Invoke(t, 1, "balanceOf", e.CommitteeHash)
c.Invoke(t, 3, "balanceOf", e.CommitteeHash) // org, com, qqq...qqq.com
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash)
topBlock := e.TopBlock(t)
expectedExpiration := topBlock.Timestamp + millisecondsInYear
@ -183,7 +187,7 @@ func TestRegisterAndRenew(t *testing.T) {
props.Add(stackitem.Make("name"), stackitem.Make("neo.com"))
props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration))
c.Invoke(t, props, "properties", "neo.com")
c.Invoke(t, 2, "balanceOf", e.CommitteeHash)
c.Invoke(t, 5, "balanceOf", e.CommitteeHash) // org, com, qqq...qqq.com, neo.com, test-domain.com
c.Invoke(t, e.CommitteeHash.BytesBE(), "ownerOf", []byte("neo.com"))
t.Run("invalid token ID", func(t *testing.T) {
@ -207,7 +211,7 @@ func TestSetGetRecord(t *testing.T) {
acc := e.NewAccount(t)
cAcc := c.WithSigners(acc)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
t.Run("set before register", func(t *testing.T) {
c.InvokeFail(t, "token not found", "setRecord", "neo.com", int64(nns.TXT), "sometext")
@ -316,7 +320,7 @@ func TestSetAdmin(t *testing.T) {
guest := e.NewAccount(t)
cGuest := c.WithSigners(guest)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cOwner.Invoke(t, true, "register", "neo.com", owner.ScriptHash())
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.ScriptHash())
@ -349,13 +353,13 @@ func TestTransfer(t *testing.T) {
to := e.NewAccount(t)
cTo := c.WithSigners(to)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cFrom.Invoke(t, true, "register", "neo.com", from.ScriptHash())
cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
cFrom.InvokeFail(t, "token not found", "transfer", to.ScriptHash(), "not.exists", nil)
c.Invoke(t, false, "transfer", to.ScriptHash(), "neo.com", nil)
cFrom.Invoke(t, true, "transfer", to.ScriptHash(), "neo.com", nil)
cFrom.Invoke(t, 1, "totalSupply")
cFrom.Invoke(t, 2, "totalSupply") // com, neo.com
cFrom.Invoke(t, to.ScriptHash().BytesBE(), "ownerOf", "neo.com")
// without onNEP11Transfer
@ -374,7 +378,7 @@ func TestTransfer(t *testing.T) {
&compiler.Options{Name: "foo"})
e.DeployContract(t, ctr, nil)
cTo.Invoke(t, true, "transfer", ctr.Hash, []byte("neo.com"), nil)
cFrom.Invoke(t, 1, "totalSupply")
cFrom.Invoke(t, 2, "totalSupply") // com, neo.com
cFrom.Invoke(t, ctr.Hash.BytesBE(), "ownerOf", []byte("neo.com"))
}
@ -387,17 +391,18 @@ func TestTokensOf(t *testing.T) {
acc2 := e.NewAccount(t)
cAcc2 := c.WithSigners(acc2)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
tld := []byte("com")
c.Invoke(t, true, "register", tld, c.CommitteeHash)
cAcc1.Invoke(t, true, "register", "neo.com", acc1.ScriptHash())
cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash())
testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
testTokensOf(t, c, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
testTokensOf(t, c, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still
testTokensOf(t, c, tld, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
testTokensOf(t, c, tld, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
testTokensOf(t, c, tld, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
testTokensOf(t, c, tld, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still
}
func testTokensOf(t *testing.T, c *neotest.ContractInvoker, result [][]byte, args ...interface{}) {
func testTokensOf(t *testing.T, c *neotest.ContractInvoker, tld []byte, result [][]byte, args ...interface{}) {
method := "tokensOf"
if len(args) == 0 {
method = "tokens"
@ -415,8 +420,13 @@ func testTokensOf(t *testing.T, c *neotest.ContractInvoker, result [][]byte, arg
require.Equal(t, result[i], iter.Value().Value())
arr = append(arr, stackitem.Make(result[i]))
}
if method == "tokens" {
require.True(t, iter.Next())
require.Equal(t, tld, iter.Value().Value())
} else {
require.False(t, iter.Next())
}
}
func TestResolve(t *testing.T) {
c := newNSClient(t)
@ -425,7 +435,7 @@ func TestResolve(t *testing.T) {
acc := e.NewAccount(t)
cAcc := c.WithSigners(acc)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cAcc.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com")

View file

@ -164,7 +164,7 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) {
e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12
// Block #13: add `.com` root to NNS.
nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13
nsCommitteeInvoker.Invoke(t, true, "register", "com", nsCommitteeInvoker.CommitteeHash) // block #13
// Block #14: register `neo.com` via NNS.
registerTxH := nsPriv0Invoker.Invoke(t, true, "register",