[#9999] nns: Add support global domain
All checks were successful
DCO action / DCO (pull_request) Successful in 56s
Tests / Tests (1.21) (pull_request) Successful in 1m40s
Tests / Tests (1.22) (pull_request) Successful in 1m43s

Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
This commit is contained in:
Alexander Chuprov 2024-07-31 17:57:20 +03:00
parent 49e5270f67
commit 8a472c0f57
3 changed files with 147 additions and 0 deletions

View file

@ -2,6 +2,7 @@ package container
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common" "git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
"github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/convert" "github.com/nspcc-dev/neo-go/pkg/interop/convert"
@ -182,6 +183,7 @@ func PutNamed(container []byte, signature interop.Signature,
needRegister bool needRegister bool
nnsContractAddr interop.Hash160 nnsContractAddr interop.Hash160
domain string domain string
globalDomain string
) )
if name != "" { if name != "" {
if zone == "" { if zone == "" {
@ -189,6 +191,12 @@ func PutNamed(container []byte, signature interop.Signature,
} }
nnsContractAddr = storage.Get(ctx, nnsContractKey).(interop.Hash160) nnsContractAddr = storage.Get(ctx, nnsContractKey).(interop.Hash160)
domain = name + "." + zone domain = name + "." + zone
tldGlobalDomain := getGlobalDomain(nnsContractAddr, zone)
if tldGlobalDomain != "" {
globalDomain = name + "." + tldGlobalDomain
checkGlobalDomainAvailable(nnsContractAddr, globalDomain)
}
needRegister = checkNiceNameAvailable(nnsContractAddr, domain) needRegister = checkNiceNameAvailable(nnsContractAddr, domain)
} }
@ -243,10 +251,81 @@ func PutNamed(container []byte, signature interop.Signature,
storage.Put(ctx, key, domain) storage.Put(ctx, key, domain)
} }
if globalDomain != "" {
if res := contract.Call(nnsContractAddr, "register", contract.All,
globalDomain, runtime.GetExecutingScriptHash(), "ops@frostfs.info",
defaultRefresh, defaultRetry, defaultExpire, defaultTTL).(bool); !res {
panic("can't register the global domain")
}
contract.Call(nnsContractAddr, "addRecord", contract.All,
domain, int64(nns.CNAME), globalDomain)
}
runtime.Log("added new container") runtime.Log("added new container")
runtime.Notify("PutSuccess", containerID, publicKey) runtime.Notify("PutSuccess", containerID, publicKey)
} }
// solveCnametgt determines the value of the txt parameter for the Cnametgt record of the specified domain.
// If the txt record is missing, the function returns an empty string.
func solveCnametgt(nnsContractAddr interop.Hash160, domain string) string {
if domain == "" {
return ""
}
res := contract.Call(nnsContractAddr, "getRecords",
contract.ReadStates|contract.AllowCall, domain, nns.TXT)
if res == nil {
return ""
}
names := res.([]string)
tgt := ""
for _, name := range names {
fragments := std.StringSplit(name, "=")
if len(fragments) != 2 {
continue
}
if fragments[0] == nns.Cnametgt {
tgt = fragments[1]
}
}
return tgt
}
// splitDomain splits domain name into parts.
func splitDomain(name string) []string {
fragments := std.StringSplit(name, ".")
return fragments
}
// getGlobalDomain returns the GlobalDomain for a specific domain.
func getGlobalDomain(nnsContractAddr interop.Hash160, zone string) string {
subDomains := splitDomain(zone)
globalDomain := ""
if len(subDomains) > 0 {
ns := subDomains[len(subDomains)-1]
if ns == "ns" && len(ns) > 1 {
ns = subDomains[len(subDomains)-2] + "." + subDomains[len(subDomains)-1]
}
if ns != "" {
globalDomain = solveCnametgt(nnsContractAddr, ns)
}
}
return globalDomain
}
// checkGlobalDomainAvailable checks if the nice name is available for the container.
// It panics if the name is taken.
func checkGlobalDomainAvailable(nnsContractAddr interop.Hash160, globalDomain string) {
if isAvail := contract.Call(nnsContractAddr, "isAvailable",
contract.ReadStates|contract.AllowCall, globalDomain).(bool); !isAvail {
panic("global domain is already taken")
}
}
// checkNiceNameAvailable checks if the nice name is available for the container. // checkNiceNameAvailable checks if the nice name is available for the container.
// It panics if the name is taken. Returned value specifies if new domain registration is needed. // It panics if the name is taken. Returned value specifies if new domain registration is needed.
func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool { func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool {
@ -301,6 +380,11 @@ func Delete(containerID []byte, signature interop.Signature, publicKey interop.P
if res != nil && std.Base58Encode(containerID) == string(res.([]any)[0].(string)) { if res != nil && std.Base58Encode(containerID) == string(res.([]any)[0].(string)) {
contract.Call(nnsContractAddr, "deleteRecords", contract.All, domain, 16 /* TXT */) contract.Call(nnsContractAddr, "deleteRecords", contract.All, domain, 16 /* TXT */)
} }
// res = contract.Call(nnsContractAddr, "getRecords", contract.ReadStates|contract.AllowCall, domain, nns.CNAME)
// if res != nil && std.Base58Encode(containerID) == string(res.([]any)[0].(string)) {
// contract.Call(nnsContractAddr, "deleteRecords", contract.All, domain, nns.CNAME)
// }
} }
removeContainer(ctx, containerID, ownerID) removeContainer(ctx, containerID, ownerID)
runtime.Log("remove container") runtime.Log("remove container")

View file

@ -69,6 +69,10 @@ const (
errInvalidDomainName = "invalid domain name format" errInvalidDomainName = "invalid domain name format"
) )
const (
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

View file

@ -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,57 @@ 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{})
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...)
})
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