[#102] nns: Support global domain
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
This commit is contained in:
parent
49e5270f67
commit
ad38f6f9ec
7 changed files with 362 additions and 2 deletions
|
@ -2,7 +2,7 @@ name: "NameService"
|
||||||
supportedstandards: ["NEP-11"]
|
supportedstandards: ["NEP-11"]
|
||||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
|
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
|
||||||
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords",
|
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords",
|
||||||
"resolve", "version"]
|
"resolve", "version","globalDomainInfo"]
|
||||||
events:
|
events:
|
||||||
- name: Transfer
|
- name: Transfer
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
| 0x20 | int | set of roots |
|
| 0x20 | int | set of roots |
|
||||||
| 0x21 + tokenKey | ByteArray | serialized NameState struct |
|
| 0x21 + tokenKey | ByteArray | serialized NameState struct |
|
||||||
| 0x22 + tokenKey + Hash160(tokenName) | Hash160 | container contract hash |
|
| 0x22 + tokenKey + Hash160(tokenName) | Hash160 | container contract hash |
|
||||||
|
| 0x23 + tokenKey + Hash160(tokenName) | bool | global domain flag |
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
26
nns/global_domain.go
Normal file
26
nns/global_domain.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package nns
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
|
||||||
|
// GlobalDomainInfo provides domain details including global zone aliases and activation status.
|
||||||
|
func GlobalDomainInfo(name string) string {
|
||||||
|
fragments := splitAndCheck(name)
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
globalDomainKey := append([]byte{prefixGlobalDomain}, getTokenKey([]byte(name))...)
|
||||||
|
status := storage.Get(ctx, globalDomainKey)
|
||||||
|
|
||||||
|
if status != nil {
|
||||||
|
tokenId := []byte(tokenIDFromName(name))
|
||||||
|
globalDomain := getRecordsByType(ctx, tokenId, name, CNAME)
|
||||||
|
if len(globalDomain) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return globalDomain[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
cnameTgt := extractCnametgt(ctx, name, fragments)
|
||||||
|
if cnameTgt == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "." + cnameTgt
|
||||||
|
}
|
|
@ -41,6 +41,9 @@ const (
|
||||||
// prefixRecord contains map from (token key + hash160(token name) + record type)
|
// prefixRecord contains map from (token key + hash160(token name) + record type)
|
||||||
// to record.
|
// to record.
|
||||||
prefixRecord byte = 0x22
|
prefixRecord byte = 0x22
|
||||||
|
//prefixGlobalDomain contains a flag indicating that this domain was created using GlobalDomain.
|
||||||
|
//This is necessary to distinguish it from regular CNAME records.
|
||||||
|
prefixGlobalDomain byte = 0x23
|
||||||
)
|
)
|
||||||
|
|
||||||
// Values constraints.
|
// Values constraints.
|
||||||
|
@ -69,6 +72,12 @@ const (
|
||||||
errInvalidDomainName = "invalid domain name format"
|
errInvalidDomainName = "invalid domain name format"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Cnametgt is a special TXT record ensuring all created subdomains point to the global domain - the value of this variable.
|
||||||
|
//It is guaranteed that two domains cannot point to the same global domain.
|
||||||
|
Cnametgt = "cnametgt"
|
||||||
|
)
|
||||||
|
|
||||||
// RecordState is a type that registered entities are saved to.
|
// RecordState is a type that registered entities are saved to.
|
||||||
type RecordState struct {
|
type RecordState struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -231,9 +240,80 @@ func IsAvailable(name string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
checkParentExists(ctx, fragments)
|
checkParentExists(ctx, fragments)
|
||||||
|
if !checkAvailableGlobalDomain(ctx, fragments) {
|
||||||
|
panic("global domain is already taken")
|
||||||
|
}
|
||||||
return storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) == nil
|
return storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckAvailableGlobalDomain - triggers a panic if the global domain name is occupied.
|
||||||
|
func CheckAvailableGlobalDomain(name string) {
|
||||||
|
fragments := splitAndCheck(name)
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
if !checkAvailableGlobalDomain(ctx, fragments) {
|
||||||
|
panic("global domain is already taken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAvailableGlobalDomain(ctx storage.Context, fragments []string) bool {
|
||||||
|
globalDomain := getGlobalDomain(ctx, fragments)
|
||||||
|
if globalDomain == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(globalDomain))...))
|
||||||
|
if nsBytes != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobalDomain returns the global domain.
|
||||||
|
func GetGlobalDomain(name string) string {
|
||||||
|
fragments := splitAndCheck(name)
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
return getGlobalDomain(ctx, fragments)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGlobalDomain(ctx storage.Context, fragments []string) string {
|
||||||
|
name := ""
|
||||||
|
for i := 1; i < len(fragments); i++ {
|
||||||
|
if name != "" {
|
||||||
|
name = name + "."
|
||||||
|
}
|
||||||
|
name = name + fragments[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractCnametgt(ctx, name, fragments)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractCnametgt returns the value of the Cnametgt TXT record.
|
||||||
|
func extractCnametgt(ctx storage.Context, name string, fragments []string) string {
|
||||||
|
tokenID := []byte(tokenIDFromName(name))
|
||||||
|
records := getRecordsByType(ctx, tokenID, name, TXT)
|
||||||
|
|
||||||
|
globalDomain := ""
|
||||||
|
for _, name := range records {
|
||||||
|
fragments := std.StringSplit(name, "=")
|
||||||
|
if len(fragments) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fragments[0] == Cnametgt {
|
||||||
|
globalDomain = fragments[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalDomain == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fragments[0] + "." + globalDomain
|
||||||
|
}
|
||||||
|
|
||||||
// checkParentExists panics if any domain from fragments doesn't exist or is expired.
|
// checkParentExists panics if any domain from fragments doesn't exist or is expired.
|
||||||
func checkParentExists(ctx storage.Context, fragments []string) {
|
func checkParentExists(ctx storage.Context, fragments []string) {
|
||||||
if dom := parentExpired(ctx, fragments); dom != "" {
|
if dom := parentExpired(ctx, fragments); dom != "" {
|
||||||
|
@ -265,10 +345,14 @@ func parentExpired(ctx storage.Context, fragments []string) string {
|
||||||
|
|
||||||
// Register registers a new domain with the specified owner and name if it's available.
|
// Register registers a 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 {
|
func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
return register(ctx, name, owner, email, refresh, retry, expire, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func register(ctx storage.Context, name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
|
||||||
fragments := splitAndCheck(name)
|
fragments := splitAndCheck(name)
|
||||||
l := len(fragments)
|
l := len(fragments)
|
||||||
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...)
|
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...)
|
||||||
ctx := storage.GetContext()
|
|
||||||
tldBytes := storage.Get(ctx, tldKey)
|
tldBytes := storage.Get(ctx, tldKey)
|
||||||
if l == 1 {
|
if l == 1 {
|
||||||
checkCommittee()
|
checkCommittee()
|
||||||
|
@ -325,6 +409,10 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
|
||||||
// NNS expiration is in milliseconds
|
// NNS expiration is in milliseconds
|
||||||
Expiration: int64(runtime.GetTime() + expire*1000),
|
Expiration: int64(runtime.GetTime() + expire*1000),
|
||||||
}
|
}
|
||||||
|
if !checkAvailableGlobalDomain(ctx, fragments) {
|
||||||
|
panic("global domain is already taken")
|
||||||
|
}
|
||||||
|
|
||||||
putNameStateWithKey(ctx, tokenKey, ns)
|
putNameStateWithKey(ctx, tokenKey, ns)
|
||||||
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
|
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
|
||||||
updateBalance(ctx, []byte(name), owner, +1)
|
updateBalance(ctx, []byte(name), owner, +1)
|
||||||
|
@ -332,6 +420,32 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteDomain(name string) {
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
deleteDomain(ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteDomain(ctx storage.Context, name string) {
|
||||||
|
nameKey := append([]byte{prefixName}, getTokenKey([]byte(name))...)
|
||||||
|
tldBytes := storage.Get(ctx, nameKey)
|
||||||
|
if tldBytes == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenId := []byte(tokenIDFromName(name))
|
||||||
|
globalDomain := getRecordsByType(ctx, tokenId, name, CNAME)
|
||||||
|
if len(globalDomain) > 0 {
|
||||||
|
DeleteRecords(name, CNAME)
|
||||||
|
globalDomainKey := append([]byte{prefixGlobalDomain}, getTokenKey([]byte(name))...)
|
||||||
|
status := storage.Get(ctx, globalDomainKey)
|
||||||
|
if status != nil {
|
||||||
|
deleteDomain(ctx, globalDomain[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DeleteRecords(name, TXT)
|
||||||
|
storage.Delete(ctx, nameKey)
|
||||||
|
}
|
||||||
|
|
||||||
// Renew increases domain expiration date.
|
// Renew increases domain expiration date.
|
||||||
func Renew(name string) int64 {
|
func Renew(name string) int64 {
|
||||||
checkDomainNameLength(name)
|
checkDomainNameLength(name)
|
||||||
|
@ -427,6 +541,20 @@ func DeleteRecords(name string, typ RecordType) {
|
||||||
ns.checkAdmin()
|
ns.checkAdmin()
|
||||||
recordsKey := getRecordsKeyByType(tokenID, name, typ)
|
recordsKey := getRecordsKeyByType(tokenID, name, typ)
|
||||||
records := storage.Find(ctx, recordsKey, storage.KeysOnly)
|
records := storage.Find(ctx, recordsKey, storage.KeysOnly)
|
||||||
|
|
||||||
|
if typ == TXT {
|
||||||
|
fragments := splitAndCheck(name)
|
||||||
|
globalDomain := getGlobalDomain(ctx, fragments)
|
||||||
|
globalDomainKey := append([]byte{prefixGlobalDomain}, getTokenKey([]byte(name))...)
|
||||||
|
status := storage.Get(ctx, globalDomainKey)
|
||||||
|
|
||||||
|
if status != nil || globalDomain != "" {
|
||||||
|
storage.Delete(ctx, globalDomainKey)
|
||||||
|
deleteDomain(ctx, globalDomain)
|
||||||
|
DeleteRecords(name, CNAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for iterator.Next(records) {
|
for iterator.Next(records) {
|
||||||
r := iterator.Value(records).(string)
|
r := iterator.Value(records).(string)
|
||||||
storage.Delete(ctx, r)
|
storage.Delete(ctx, r)
|
||||||
|
@ -574,6 +702,24 @@ func addRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalDomainKey := append([]byte{prefixGlobalDomain}, getTokenKey([]byte(name))...)
|
||||||
|
status := storage.Get(ctx, globalDomainKey)
|
||||||
|
fragments := splitAndCheck(name)
|
||||||
|
globalDomain := getGlobalDomain(ctx, fragments)
|
||||||
|
|
||||||
|
if status == nil && globalDomain != "" {
|
||||||
|
var id byte
|
||||||
|
recordKey := getIdRecordKey(tokenId, string(tokenId), SOA, id)
|
||||||
|
|
||||||
|
recBytes := storage.Get(ctx, recordKey)
|
||||||
|
rec := std.Deserialize(recBytes.([]byte)).(RecordState)
|
||||||
|
split := std.StringSplitNonEmpty(rec.Data, " ")
|
||||||
|
register(ctx, globalDomain, OwnerOf(tokenId), split[1], std.Atoi(split[3], 10), std.Atoi(split[4], 10),
|
||||||
|
std.Atoi(split[5], 10), std.Atoi(split[6], 10))
|
||||||
|
|
||||||
|
storage.Put(ctx, globalDomainKey, true)
|
||||||
|
putCnameRecord(ctx, name, globalDomain)
|
||||||
|
}
|
||||||
if typ == CNAME && id != 0 {
|
if typ == CNAME && id != 0 {
|
||||||
panic("you shouldn't have more than one CNAME record")
|
panic("you shouldn't have more than one CNAME record")
|
||||||
}
|
}
|
||||||
|
@ -614,6 +760,21 @@ func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expir
|
||||||
storage.Put(ctx, recordKey, recBytes)
|
storage.Put(ctx, recordKey, recBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// putCnameRecord stores CNAME domain record.
|
||||||
|
func putCnameRecord(ctx storage.Context, name, data string) {
|
||||||
|
var id byte
|
||||||
|
tokenId := []byte(tokenIDFromName(name))
|
||||||
|
recordKey := getIdRecordKey(tokenId, name, CNAME, id)
|
||||||
|
rs := RecordState{
|
||||||
|
Name: name,
|
||||||
|
Type: CNAME,
|
||||||
|
ID: id,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
recBytes := std.Serialize(rs)
|
||||||
|
storage.Put(ctx, recordKey, recBytes)
|
||||||
|
}
|
||||||
|
|
||||||
// updateSoaSerial stores soa domain record.
|
// updateSoaSerial stores soa domain record.
|
||||||
func updateSoaSerial(ctx storage.Context, tokenId []byte) {
|
func updateSoaSerial(ctx storage.Context, tokenId []byte) {
|
||||||
var id byte
|
var id byte
|
||||||
|
|
|
@ -70,6 +70,11 @@ func (c *ContractReader) GetRecords(name string, typ *big.Int) ([]stackitem.Item
|
||||||
return unwrap.Array(c.invoker.Call(c.hash, "getRecords", name, typ))
|
return unwrap.Array(c.invoker.Call(c.hash, "getRecords", name, typ))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalDomainInfo invokes `globalDomainInfo` method of contract.
|
||||||
|
func (c *ContractReader) GlobalDomainInfo(name string) (string, error) {
|
||||||
|
return unwrap.UTF8String(c.invoker.Call(c.hash, "globalDomainInfo", name))
|
||||||
|
}
|
||||||
|
|
||||||
// IsAvailable invokes `isAvailable` method of contract.
|
// IsAvailable invokes `isAvailable` method of contract.
|
||||||
func (c *ContractReader) IsAvailable(name string) (bool, error) {
|
func (c *ContractReader) IsAvailable(name string) (bool, error) {
|
||||||
return unwrap.Bool(c.invoker.Call(c.hash, "isAvailable", name))
|
return unwrap.Bool(c.invoker.Call(c.hash, "isAvailable", name))
|
||||||
|
@ -121,6 +126,50 @@ func (c *Contract) AddRecordUnsigned(name string, typ *big.Int, data string) (*t
|
||||||
return c.actor.MakeUnsignedCall(c.hash, "addRecord", nil, name, typ, data)
|
return c.actor.MakeUnsignedCall(c.hash, "addRecord", nil, name, typ, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckAvailableGlobalDomain creates a transaction invoking `checkAvailableGlobalDomain` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) CheckAvailableGlobalDomain(name string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "checkAvailableGlobalDomain", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAvailableGlobalDomainTransaction creates a transaction invoking `checkAvailableGlobalDomain` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) CheckAvailableGlobalDomainTransaction(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "checkAvailableGlobalDomain", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAvailableGlobalDomainUnsigned creates a transaction invoking `checkAvailableGlobalDomain` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) CheckAvailableGlobalDomainUnsigned(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "checkAvailableGlobalDomain", nil, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDomain creates a transaction invoking `deleteDomain` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) DeleteDomain(name string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "deleteDomain", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDomainTransaction creates a transaction invoking `deleteDomain` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) DeleteDomainTransaction(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "deleteDomain", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDomainUnsigned creates a transaction invoking `deleteDomain` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) DeleteDomainUnsigned(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "deleteDomain", nil, name)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteRecords creates a transaction invoking `deleteRecords` method of the contract.
|
// DeleteRecords creates a transaction invoking `deleteRecords` method of the contract.
|
||||||
// This transaction is signed and immediately sent to the network.
|
// This transaction is signed and immediately sent to the network.
|
||||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
@ -165,6 +214,28 @@ func (c *Contract) GetAllRecordsUnsigned(name string) (*transaction.Transaction,
|
||||||
return c.actor.MakeUnsignedCall(c.hash, "getAllRecords", nil, name)
|
return c.actor.MakeUnsignedCall(c.hash, "getAllRecords", nil, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGlobalDomain creates a transaction invoking `getGlobalDomain` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) GetGlobalDomain(name string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "getGlobalDomain", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobalDomainTransaction creates a transaction invoking `getGlobalDomain` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) GetGlobalDomainTransaction(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "getGlobalDomain", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobalDomainUnsigned creates a transaction invoking `getGlobalDomain` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) GetGlobalDomainUnsigned(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "getGlobalDomain", nil, name)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Contract) scriptForRegister(name string, owner util.Uint160, email string, refresh *big.Int, retry *big.Int, expire *big.Int, ttl *big.Int) ([]byte, error) {
|
func (c *Contract) scriptForRegister(name string, owner util.Uint160, email string, refresh *big.Int, retry *big.Int, expire *big.Int, ttl *big.Int) ([]byte, error) {
|
||||||
return smartcontract.CreateCallWithAssertScript(c.hash, "register", name, owner, email, refresh, retry, expire, ttl)
|
return smartcontract.CreateCallWithAssertScript(c.hash, "register", name, owner, email, refresh, retry, expire, ttl)
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,14 @@ func checkContainerList(t *testing.T, c *neotest.ContractInvoker, expected [][]b
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// default SOA record field values
|
||||||
|
defaultRefresh = 3600 // 1 hour
|
||||||
|
defaultRetry = 600 // 10 min
|
||||||
|
defaultExpire = 3600 * 24 * 365 * 10 // 10 years
|
||||||
|
defaultTTL = 3600 // 1 hour
|
||||||
|
)
|
||||||
|
|
||||||
func TestContainerPut(t *testing.T) {
|
func TestContainerPut(t *testing.T) {
|
||||||
c, cBal, _ := newContainerInvoker(t)
|
c, cBal, _ := newContainerInvoker(t)
|
||||||
|
|
||||||
|
@ -233,6 +241,64 @@ func TestContainerPut(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("create global domain", func(t *testing.T) {
|
||||||
|
ctrNNS := neotest.CompileFile(t, c.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
|
||||||
|
nnsHash := ctrNNS.Hash
|
||||||
|
cNNS := c.CommitteeInvoker(nnsHash)
|
||||||
|
cNNS.Invoke(t, true, "register",
|
||||||
|
"animals", c.CommitteeHash,
|
||||||
|
"whateveriwant@world.com", int64(0), int64(0), int64(100_000), int64(0))
|
||||||
|
|
||||||
|
cNNS.Invoke(t, true, "register",
|
||||||
|
"ns", c.CommitteeHash,
|
||||||
|
"whateveriwant@world.com", int64(0), int64(0), int64(100_000), int64(0))
|
||||||
|
|
||||||
|
cNNS.Invoke(t, true, "register",
|
||||||
|
"poland.ns", c.CommitteeHash,
|
||||||
|
"whateveriwant@world.com", int64(0), int64(0), int64(100_000), int64(0))
|
||||||
|
|
||||||
|
cNNS.Invoke(t, true, "register",
|
||||||
|
"sweden.ns", c.CommitteeHash,
|
||||||
|
"whateveriwant@world.com", int64(0), int64(0), int64(100_000), int64(0))
|
||||||
|
|
||||||
|
cNNS.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"poland.ns", int64(nns.TXT), nns.Cnametgt+"=animals")
|
||||||
|
|
||||||
|
cNNS.Invoke(t, stackitem.Null{}, "addRecord", "poland.ns", int64(nns.TXT), "random-record")
|
||||||
|
|
||||||
|
cNNS.Invoke(t, stackitem.Null{}, "addRecord", "poland.ns", int64(nns.TXT), "ne-qqq=random-record2")
|
||||||
|
cNNS.Invoke(t, stackitem.Null{}, "addRecord", "sweden.ns", int64(nns.TXT), nns.Cnametgt+"=animals")
|
||||||
|
|
||||||
|
balanceMint(t, cBal, acc, (containerFee+containerAliasFee)*5, []byte{})
|
||||||
|
cNNS.Invoke(t, true, "isAvailable", "bober.animals")
|
||||||
|
putArgs := []any{cnt.value, cnt.sig, cnt.pub, cnt.token, "bober", "poland.ns"}
|
||||||
|
c3 := c.WithSigners(c.Committee, acc)
|
||||||
|
c3.Invoke(t, stackitem.Null{}, "putNamed", putArgs...)
|
||||||
|
b := c3.TopBlock(t)
|
||||||
|
|
||||||
|
expected := stackitem.NewArray([]stackitem.Item{stackitem.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("bober.animals ops@frostfs.info %d %d %d %d %d",
|
||||||
|
b.Timestamp, defaultRefresh, defaultRetry, defaultExpire, defaultTTL)))})
|
||||||
|
cNNS.Invoke(t, expected, "getRecords", "bober.animals", int64(nns.SOA))
|
||||||
|
|
||||||
|
expected = stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewBuffer([]byte("bober.animals")),
|
||||||
|
})
|
||||||
|
|
||||||
|
cNNS.Invoke(t, false, "isAvailable", "bober.animals")
|
||||||
|
|
||||||
|
cNNS.Invoke(t, expected, "getRecords", "bober.poland.ns", int64(nns.CNAME))
|
||||||
|
|
||||||
|
putArgs = []any{cnt.value, cnt.sig, cnt.pub, cnt.token, "bober", "sweden.ns"}
|
||||||
|
|
||||||
|
c3.InvokeFail(t, "global domain is already taken", "putNamed", putArgs...)
|
||||||
|
cNNS.InvokeFail(t, "global domain is already taken", "checkAvailableGlobalDomain", "bober.poland.ns")
|
||||||
|
|
||||||
|
c3.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
|
||||||
|
cNNS.Invoke(t, true, "isAvailable", "bober.animals")
|
||||||
|
cNNS.Invoke(t, stackitem.Null{}, "checkAvailableGlobalDomain", "bober.poland.ns")
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("gas costs are the same for all containers in block", func(t *testing.T) {
|
t.Run("gas costs are the same for all containers in block", func(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
containerPerBlock = 512
|
containerPerBlock = 512
|
||||||
|
|
|
@ -99,6 +99,10 @@ func TestNNSRegister(t *testing.T) {
|
||||||
"com", accTop.ScriptHash(),
|
"com", accTop.ScriptHash(),
|
||||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||||
|
|
||||||
|
c1.Invoke(t, true, "register",
|
||||||
|
"globaldomain.com", accTop.ScriptHash(),
|
||||||
|
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||||
|
|
||||||
acc := c.NewAccount(t)
|
acc := c.NewAccount(t)
|
||||||
c2 := c.WithSigners(c.Committee, acc)
|
c2 := c.WithSigners(c.Committee, acc)
|
||||||
c2.InvokeFail(t, "not witnessed by admin", "register",
|
c2.InvokeFail(t, "not witnessed by admin", "register",
|
||||||
|
@ -157,6 +161,21 @@ func TestNNSRegister(t *testing.T) {
|
||||||
stackitem.NewByteArray([]byte("second TXT record")),
|
stackitem.NewByteArray([]byte("second TXT record")),
|
||||||
})
|
})
|
||||||
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
|
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
|
||||||
|
|
||||||
|
c3.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), nns.Cnametgt+"=globaldomain.com")
|
||||||
|
c.Invoke(t, true, "isAvailable", "dom.testdomain.com")
|
||||||
|
|
||||||
|
c3.Invoke(t, true, "register",
|
||||||
|
"dom.testdomain.com", acc.ScriptHash(),
|
||||||
|
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||||
|
|
||||||
|
c3.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"dom.testdomain.com", int64(nns.TXT), "CID")
|
||||||
|
|
||||||
|
c.InvokeFail(t, "global domain is already taken", "isAvailable", "dom.testdomain.com")
|
||||||
|
c.Invoke(t, false, "isAvailable", "dom.globaldomain.com")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTLDRecord(t *testing.T) {
|
func TestTLDRecord(t *testing.T) {
|
||||||
|
@ -365,6 +384,10 @@ func TestNNSIsAvailable(t *testing.T) {
|
||||||
"domain.com", acc.ScriptHash(),
|
"domain.com", acc.ScriptHash(),
|
||||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||||
|
|
||||||
|
c1.Invoke(t, true, "register",
|
||||||
|
"globaldomain.com", acc.ScriptHash(),
|
||||||
|
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||||
|
|
||||||
c.Invoke(t, false, "isAvailable", "domain.com")
|
c.Invoke(t, false, "isAvailable", "domain.com")
|
||||||
|
|
||||||
c.Invoke(t, true, "isAvailable", "dom.domain.com")
|
c.Invoke(t, true, "isAvailable", "dom.domain.com")
|
||||||
|
@ -376,6 +399,18 @@ func TestNNSIsAvailable(t *testing.T) {
|
||||||
c.Invoke(t, false, "isAvailable", "dom.domain.com")
|
c.Invoke(t, false, "isAvailable", "dom.domain.com")
|
||||||
c.Invoke(t, true, "isAvailable", "dom.dom.domain.com")
|
c.Invoke(t, true, "isAvailable", "dom.dom.domain.com")
|
||||||
|
|
||||||
|
c1.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"dom.domain.com", int64(nns.TXT), nns.Cnametgt+"=globaldomain.com")
|
||||||
|
c.Invoke(t, true, "isAvailable", "dom.dom.domain.com")
|
||||||
|
c.Invoke(t, stackitem.Null{}, "checkAvailableGlobalDomain", "dom.domain.com")
|
||||||
|
|
||||||
|
c1.Invoke(t, true, "register",
|
||||||
|
"dom.globaldomain.com", acc.ScriptHash(),
|
||||||
|
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||||
|
|
||||||
|
c.InvokeFail(t, "global domain is already taken", "isAvailable", "dom.dom.domain.com")
|
||||||
|
c.InvokeFail(t, "global domain is already taken", "checkAvailableGlobalDomain", "dom.dom.domain.com")
|
||||||
|
|
||||||
c.InvokeFail(t, "domain name too long", "isAvailable", getTooLongDomainName(255))
|
c.InvokeFail(t, "domain name too long", "isAvailable", getTooLongDomainName(255))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue