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 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. // Roots returns iterator over a set of NameService roots.
func Roots() iterator.Iterator { func Roots() iterator.Iterator {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -224,15 +208,36 @@ func IsAvailable(name string) bool {
panic("invalid domain name format") panic("invalid domain name format")
} }
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil { l := len(fragments)
panic("root not found") 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))...)) nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...))
if nsBytes == nil { if nsBytes == nil {
return true return true
} }
ns := std.Deserialize(nsBytes.([]byte)).(NameState) 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. // 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 { if fragments == nil {
panic("invalid domain name format") panic("invalid domain name format")
} }
l := len(fragments)
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...)
ctx := storage.GetContext() ctx := storage.GetContext()
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil { tldBytes := storage.Get(ctx, tldKey)
panic("root not found") 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) { if !isValid(owner) {
@ -548,9 +567,6 @@ func splitAndCheck(name string, allowMultipleFragments bool) []string {
} }
fragments := std.StringSplit(name, ".") fragments := std.StringSplit(name, ".")
l = len(fragments) l = len(fragments)
if l < 2 {
return nil
}
if l > 2 && !allowMultipleFragments { if l > 2 && !allowMultipleFragments {
return nil return nil
} }
@ -679,6 +695,9 @@ func tokenIDFromName(name string) string {
panic("invalid domain name format") panic("invalid domain name format")
} }
l := len(fragments) l := len(fragments)
if l == 1 {
return name
}
return name[len(name)-(len(fragments[l-1])+len(fragments[l-2])+1):] 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") c.Invoke(t, 0, "totalSupply")
} }
func TestAddRoot(t *testing.T) { func TestRegisterTLD(t *testing.T) {
c := newNSClient(t) c := newNSClient(t)
t.Run("invalid format", func(t *testing.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) { t.Run("not signed by committee", func(t *testing.T) {
acc := c.NewAccount(t) acc := c.NewAccount(t)
c := c.WithSigners(acc) 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) { 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) acc := e.NewAccount(t)
cAcc := c.WithSigners(acc) 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, true, "register", "first.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext") cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
b1 := e.TopBlock(t) b1 := e.TopBlock(t)
@ -107,7 +107,7 @@ func TestExpiration(t *testing.T) {
b2.PrevHash = b1.Hash() b2.PrevHash = b1.Hash()
b2.Timestamp = b1.Timestamp + 10000 b2.Timestamp = b1.Timestamp + 10000
require.NoError(t, bc.AddBlock(e.SignBlock(b2))) 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") tx = cAcc.PrepareInvoke(t, "isAvailable", "first.com")
b3 := e.NewUnsignedBlock(t, tx) b3 := e.NewUnsignedBlock(t, tx)
@ -115,7 +115,7 @@ func TestExpiration(t *testing.T) {
b3.PrevHash = b2.Hash() b3.PrevHash = b2.Hash()
b3.Timestamp = b1.Timestamp + (millisecondsInYear + 1) b3.Timestamp = b1.Timestamp + (millisecondsInYear + 1)
require.NoError(t, bc.AddBlock(e.SignBlock(b3))) 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") tx = cAcc.PrepareInvoke(t, "isAvailable", "second.com")
b4 := e.NewUnsignedBlock(t, tx) b4 := e.NewUnsignedBlock(t, tx)
@ -123,7 +123,7 @@ func TestExpiration(t *testing.T) {
b4.PrevHash = b3.Hash() b4.PrevHash = b3.Hash()
b4.Timestamp = b3.Timestamp + 1000 b4.Timestamp = b3.Timestamp + 1000
require.NoError(t, bc.AddBlock(e.SignBlock(b4))) 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)) tx = cAcc.PrepareInvoke(t, "getRecord", "first.com", int64(nns.TXT))
b5 := e.NewUnsignedBlock(t, tx) b5 := e.NewUnsignedBlock(t, tx)
@ -133,8 +133,12 @@ func TestExpiration(t *testing.T) {
require.NoError(t, bc.AddBlock(e.SignBlock(b5))) require.NoError(t, bc.AddBlock(e.SignBlock(b5)))
e.CheckFault(t, tx.Hash(), "name has expired") e.CheckFault(t, tx.Hash(), "name has expired")
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash()) // Re-register. // TODO: According to the new code, we can't re-register expired "com" TLD, because it's already registered; at the
cAcc.Invoke(t, stackitem.Null{}, "resolve", "first.com", int64(nns.TXT)) // 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 ( const (
@ -146,10 +150,10 @@ func TestRegisterAndRenew(t *testing.T) {
c := newNSClient(t) c := newNSClient(t)
e := c.Executor e := c.Executor
c.InvokeFail(t, "root not found", "isAvailable", "neo.com") c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
c.Invoke(t, stackitem.Null{}, "addRoot", "org") c.Invoke(t, true, "register", "org", c.CommitteeHash)
c.InvokeFail(t, "root not found", "isAvailable", "neo.com") c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
c.Invoke(t, stackitem.Null{}, "addRoot", "com") c.Invoke(t, true, "register", "com", c.CommitteeHash)
c.Invoke(t, true, "isAvailable", "neo.com") c.Invoke(t, true, "isAvailable", "neo.com")
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash) 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) 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.InvokeFail(t, "invalid domain name format", "register", maxLenFragment+"q.com", e.CommitteeHash)
c.Invoke(t, true, "isAvailable", "neo.com") 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) c.Invoke(t, true, "register", "neo.com", e.CommitteeHash)
topBlock := e.TopBlock(t) topBlock := e.TopBlock(t)
expectedExpiration := topBlock.Timestamp + millisecondsInYear 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("name"), stackitem.Make("neo.com"))
props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration)) props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration))
c.Invoke(t, props, "properties", "neo.com") 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")) c.Invoke(t, e.CommitteeHash.BytesBE(), "ownerOf", []byte("neo.com"))
t.Run("invalid token ID", func(t *testing.T) { t.Run("invalid token ID", func(t *testing.T) {
@ -207,7 +211,7 @@ func TestSetGetRecord(t *testing.T) {
acc := e.NewAccount(t) acc := e.NewAccount(t)
cAcc := c.WithSigners(acc) 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) { t.Run("set before register", func(t *testing.T) {
c.InvokeFail(t, "token not found", "setRecord", "neo.com", int64(nns.TXT), "sometext") 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) guest := e.NewAccount(t)
cGuest := c.WithSigners(guest) 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()) cOwner.Invoke(t, true, "register", "neo.com", owner.ScriptHash())
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.ScriptHash()) cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.ScriptHash())
@ -349,13 +353,13 @@ func TestTransfer(t *testing.T) {
to := e.NewAccount(t) to := e.NewAccount(t)
cTo := c.WithSigners(to) 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, true, "register", "neo.com", from.ScriptHash())
cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") 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) cFrom.InvokeFail(t, "token not found", "transfer", to.ScriptHash(), "not.exists", nil)
c.Invoke(t, false, "transfer", to.ScriptHash(), "neo.com", nil) c.Invoke(t, false, "transfer", to.ScriptHash(), "neo.com", nil)
cFrom.Invoke(t, true, "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") cFrom.Invoke(t, to.ScriptHash().BytesBE(), "ownerOf", "neo.com")
// without onNEP11Transfer // without onNEP11Transfer
@ -374,7 +378,7 @@ func TestTransfer(t *testing.T) {
&compiler.Options{Name: "foo"}) &compiler.Options{Name: "foo"})
e.DeployContract(t, ctr, nil) e.DeployContract(t, ctr, nil)
cTo.Invoke(t, true, "transfer", ctr.Hash, []byte("neo.com"), 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")) cFrom.Invoke(t, ctr.Hash.BytesBE(), "ownerOf", []byte("neo.com"))
} }
@ -387,17 +391,18 @@ func TestTokensOf(t *testing.T) {
acc2 := e.NewAccount(t) acc2 := e.NewAccount(t)
cAcc2 := c.WithSigners(acc2) 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()) cAcc1.Invoke(t, true, "register", "neo.com", acc1.ScriptHash())
cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash()) cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash())
testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE()) testTokensOf(t, c, tld, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE()) testTokensOf(t, c, tld, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
testTokensOf(t, c, [][]byte{[]byte("neo.com"), []byte("nspcc.com")}) testTokensOf(t, c, tld, [][]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{}, 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" method := "tokensOf"
if len(args) == 0 { if len(args) == 0 {
method = "tokens" method = "tokens"
@ -415,7 +420,12 @@ func testTokensOf(t *testing.T, c *neotest.ContractInvoker, result [][]byte, arg
require.Equal(t, result[i], iter.Value().Value()) require.Equal(t, result[i], iter.Value().Value())
arr = append(arr, stackitem.Make(result[i])) 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()) require.False(t, iter.Next())
}
} }
func TestResolve(t *testing.T) { func TestResolve(t *testing.T) {
@ -425,7 +435,7 @@ func TestResolve(t *testing.T) {
acc := e.NewAccount(t) acc := e.NewAccount(t)
cAcc := c.WithSigners(acc) 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, 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.A), "1.2.3.4")
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com") 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 e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12
// Block #13: add `.com` root to NNS. // 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. // Block #14: register `neo.com` via NNS.
registerTxH := nsPriv0Invoker.Invoke(t, true, "register", registerTxH := nsPriv0Invoker.Invoke(t, true, "register",