frostfs-contract/frostfsid/frostfsid_contract.go

859 lines
21 KiB
Go
Raw Normal View History

package frostfsid
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"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/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
type (
Subject struct {
PrimaryKey interop.PublicKey
AdditionalKeys []interop.PublicKey
Namespace string
Name string
KV map[string]string
}
SubjectExtended struct {
PrimaryKey interop.PublicKey
AdditionalKeys []interop.PublicKey
Namespace string
Name string
KV map[string]string
Groups []Group
}
Namespace struct {
Name string
}
NamespaceExtended struct {
Name string
GroupsCount int
SubjectsCount int
}
Group struct {
Name string
Namespace string
}
GroupExtended struct {
Name string
Namespace string
SubjectsCount int
}
)
const (
ownerKeysPrefix = 'o'
subjectKeysPrefix = 's'
additionalKeysPrefix = 'a'
namespaceKeysPrefix = 'n'
namespaceSubjectsKeysPrefix = 'N'
namespaceSubjectsNamesPrefix = 'l'
groupKeysPrefix = 'g'
groupSubjectsKeysPrefix = 'G'
)
func _deploy(data any, isUpdate bool) {
ctx := storage.GetContext()
args := data.(struct {
owners []interop.Hash160
})
for _, owner := range args.owners {
if len(owner) != interop.Hash160Len {
panic("incorrect length of owner addresses")
}
storage.Put(ctx, ownerKey(owner), []byte{1})
}
runtime.Log("frostfsid contract initialized")
}
func AddOwner(addr interop.Hash160) {
ctx := storage.GetContext()
if !common.HasUpdateAccess() {
panic("not witnessed")
}
storage.Put(ctx, ownerKey(addr), []byte{1})
}
func DeleteOwner(addr interop.Hash160) {
ctx := storage.GetContext()
if !common.HasUpdateAccess() {
panic("not witnessed")
}
storage.Delete(ctx, ownerKey(addr))
}
func ListOwners() iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, []byte{ownerKeysPrefix}, storage.KeysOnly|storage.RemovePrefix)
}
// Update method updates contract source code and manifest. It can be invoked
// only by committee.
func Update(script []byte, manifest []byte, data any) {
if !common.HasUpdateAccess() {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
runtime.Log("frostfsid contract updated")
}
// Version returns the version of the contract.
func Version() int {
return common.Version
}
func CreateSubject(key interop.PublicKey) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(key) != interop.PublicKeyCompressedLen {
panic("incorrect public key length")
}
addr := contract.CreateStandardAccount(key)
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data != nil {
panic("subject already exists")
}
saPrefix := subjectAdditionalPrefix(key)
it := storage.Find(ctx, saPrefix, storage.KeysOnly)
for iterator.Next(it) {
panic("key is occupied")
}
subj := Subject{
PrimaryKey: key,
}
storage.Put(ctx, sKey, std.Serialize(subj))
runtime.Notify("CreateSubject", interop.Hash160(addr))
}
func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
if len(key) != interop.PublicKeyCompressedLen {
panic("incorrect public key length")
}
saKey := subjectAdditionalKey(key, addr)
data := storage.Get(ctx, saKey).([]byte)
if data != nil {
panic("key already added")
}
storage.Put(ctx, saKey, []byte{1})
sKey := subjectKeyFromAddr(addr)
data = storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("address not found")
}
subject := std.Deserialize(data).(Subject)
subject.AdditionalKeys = append(subject.AdditionalKeys, key)
storage.Put(ctx, sKey, std.Serialize(subject))
runtime.Notify("AddSubjectKey", addr, key)
}
func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
if len(key) != interop.PublicKeyCompressedLen {
panic("incorrect public key length")
}
saKey := subjectAdditionalKey(key, addr)
data := storage.Get(ctx, saKey).([]byte)
if data == nil {
panic("key already removed")
}
storage.Delete(ctx, saKey)
sKey := subjectKeyFromAddr(addr)
data = storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("address not found")
}
subject := std.Deserialize(data).(Subject)
var additionalKeys []interop.PublicKey
for i := 0; i < len(subject.AdditionalKeys); i++ {
if !common.BytesEqual(subject.AdditionalKeys[i], key) {
additionalKeys = append(additionalKeys, subject.AdditionalKeys[i])
}
}
subject.AdditionalKeys = additionalKeys
storage.Put(ctx, sKey, std.Serialize(subject))
runtime.Notify("RemoveSubjectKey", addr, key)
}
func SetSubjectName(addr interop.Hash160, name string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
oldName := subject.Name
subject.Name = name
storage.Put(ctx, sKey, std.Serialize(subject))
updateNamespaceSubjectName(ctx, subject, oldName)
runtime.Notify("SetSubjectName", addr, name)
}
func SetSubjectKV(addr interop.Hash160, key, val string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
if subject.KV == nil {
subject.KV = map[string]string{}
}
subject.KV[key] = val
storage.Put(ctx, sKey, std.Serialize(subject))
runtime.Notify("SetSubjectKV", addr, key, val)
}
func DeleteSubjectKV(addr interop.Hash160, key string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
delete(subject.KV, key)
storage.Put(ctx, sKey, std.Serialize(subject))
runtime.Notify("DeleteSubjectKV", addr, key)
}
func DeleteSubject(addr interop.Hash160) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
return
}
subj := std.Deserialize(data).(Subject)
for i := 0; i < len(subj.AdditionalKeys); i++ {
storage.Delete(ctx, subjectAdditionalKey(subj.AdditionalKeys[i], addr))
}
storage.Delete(ctx, sKey)
if subj.Namespace != "" {
removeSubjectFromNamespace(ctx, subj.Namespace, addr)
}
deleteNamespaceSubjectName(ctx, subj.Namespace, subj.Name)
runtime.Notify("DeleteSubject", addr)
}
func GetSubject(addr interop.Hash160) Subject {
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
ctx := storage.GetReadOnlyContext()
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
return std.Deserialize(data).(Subject)
}
func GetSubjectExtended(addr interop.Hash160) SubjectExtended {
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
ctx := storage.GetReadOnlyContext()
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subj := std.Deserialize(data).(Subject)
var groups []Group
if subj.Namespace != "" {
nsHash := ripemd160Hash(subj.Namespace)
it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
groupHash := iterator.Value(it).([]byte)
data = storage.Get(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr)).([]byte)
if data != nil {
data = storage.Get(ctx, groupKeyFromHashes(nsHash, groupHash)).([]byte)
if data == nil {
panic("group not found")
}
groups = append(groups, std.Deserialize(data).(Group))
}
}
}
subjExt := SubjectExtended{
PrimaryKey: subj.PrimaryKey,
AdditionalKeys: subj.AdditionalKeys,
Namespace: subj.Namespace,
Name: subj.Name,
KV: subj.KV,
Groups: groups,
}
return subjExt
}
func GetSubjectByKey(key interop.PublicKey) Subject {
if len(key) != interop.PublicKeyCompressedLen {
panic("incorrect key length")
}
ctx := storage.GetReadOnlyContext()
sKey := subjectKey(key)
data := storage.Get(ctx, sKey).([]byte)
if data != nil {
return std.Deserialize(data).(Subject)
}
saPrefix := subjectAdditionalPrefix(key)
it := storage.Find(ctx, saPrefix, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
addr := iterator.Value(it).([]byte)
sKey = subjectKeyFromAddr(addr)
data = storage.Get(ctx, sKey).([]byte)
if data != nil {
return std.Deserialize(data).(Subject)
}
break
}
panic("subject not found")
}
func GetSubjectKeyByName(ns, name string) interop.PublicKey {
if ns == "" || name == "" {
panic("invalid namespace or name")
}
ctx := storage.GetReadOnlyContext()
nsSubjNameKey := namespaceSubjectNameKey(ns, name)
subjKey := storage.Get(ctx, nsSubjNameKey).(interop.PublicKey)
if subjKey == nil {
panic("subject not found")
}
return subjKey
}
func ListSubjects() iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, []byte{subjectKeysPrefix}, storage.KeysOnly|storage.RemovePrefix)
}
func CreateNamespace(ns string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if ns == "" {
panic("invalid namespace name")
}
nsKey := namespaceKey(ns)
data := storage.Get(ctx, nsKey).([]byte)
if data != nil {
panic("namespace already exists")
}
namespace := Namespace{
Name: ns,
}
storage.Put(ctx, nsKey, std.Serialize(namespace))
runtime.Notify("CreateNamespace", ns)
}
func GetNamespace(ns string) Namespace {
ctx := storage.GetReadOnlyContext()
nsKey := namespaceKey(ns)
data := storage.Get(ctx, nsKey).([]byte)
if data == nil {
panic("namespace not found")
}
return std.Deserialize(data).(Namespace)
}
func GetNamespaceExtended(ns string) NamespaceExtended {
ctx := storage.GetReadOnlyContext()
nsKey := namespaceKey(ns)
data := storage.Get(ctx, nsKey).([]byte)
if data == nil {
panic("namespace not found")
}
namespace := std.Deserialize(data).(Namespace)
nsExtended := NamespaceExtended{
Name: namespace.Name,
}
it := storage.Find(ctx, groupPrefix(ns), storage.KeysOnly)
for iterator.Next(it) {
nsExtended.GroupsCount += 1
}
it = storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly)
for iterator.Next(it) {
nsExtended.SubjectsCount += 1
}
return nsExtended
}
func ListNamespaces() iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, []byte{namespaceKeysPrefix}, storage.ValuesOnly|storage.DeserializeValues)
}
func AddSubjectToNamespace(addr interop.Hash160, ns string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if ns == "" {
panic("invalid namespace name")
}
if len(addr) != interop.Hash160Len {
panic("invalid address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == ns {
panic("subject already added")
}
if subject.Namespace != "" {
panic("subject cannot be moved to another namespace")
}
subject.Namespace = ns
storage.Put(ctx, sKey, std.Serialize(subject))
data = storage.Get(ctx, namespaceKey(ns)).([]byte)
if data == nil {
panic("namespace not found")
}
nsSubjKey := namespaceSubjectKey(ns, addr)
storage.Put(ctx, nsSubjKey, []byte{1})
setNamespaceSubjectName(ctx, subject)
runtime.Notify("AddSubjectToNamespace", addr, ns)
}
func RemoveSubjectFromNamespace(addr interop.Hash160) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("invalid address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == "" {
panic("subject does not belong to any namespace")
}
removeSubjectFromNamespace(ctx, subject.Namespace, addr)
deleteNamespaceSubjectName(ctx, subject.Namespace, subject.Name)
subject.Namespace = ""
storage.Put(ctx, sKey, std.Serialize(subject))
runtime.Notify("RemoveSubjectFromNamespace", addr, subject.Namespace)
}
func ListNamespaceSubjects(ns string) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly|storage.RemovePrefix)
}
func CreateGroup(ns, group string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if ns == "" {
panic("invalid namespace name")
}
if group == "" {
panic("invalid group name")
}
nsKey := namespaceKey(ns)
data := storage.Get(ctx, nsKey).([]byte)
if data == nil {
panic("namespace not found")
}
gr := Group{
Name: group,
Namespace: ns,
}
gKey := groupKey(ns, group)
data = storage.Get(ctx, gKey).([]byte)
if data != nil {
panic("group already exists")
}
storage.Put(ctx, gKey, std.Serialize(gr))
runtime.Notify("CreateGroup", ns, group)
}
func GetGroup(ns, group string) Group {
ctx := storage.GetReadOnlyContext()
gKey := groupKey(ns, group)
data := storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
return std.Deserialize(data).(Group)
}
func GetGroupExtended(ns, group string) GroupExtended {
ctx := storage.GetReadOnlyContext()
gKey := groupKey(ns, group)
data := storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
gr := std.Deserialize(data).(Group)
grExtended := GroupExtended{
Name: gr.Name,
Namespace: gr.Namespace,
}
it := storage.Find(ctx, groupSubjectPrefix(ns, group), storage.KeysOnly)
for iterator.Next(it) {
grExtended.SubjectsCount += 1
}
return grExtended
}
func ListGroups(ns string) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, groupPrefix(ns), storage.ValuesOnly|storage.DeserializeValues)
}
func AddSubjectToGroup(addr interop.Hash160, group string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("invalid address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == "" {
panic("subject does not belong to any namespace")
}
nSubjKey := namespaceSubjectKey(subject.Namespace, addr)
storage.Put(ctx, nSubjKey, []byte{1})
gKey := groupKey(subject.Namespace, group)
data = storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
gsKey := groupSubjectKey(subject.Namespace, group, addr)
storage.Put(ctx, gsKey, []byte{1})
runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, group)
}
func RemoveSubjectFromGroup(addr interop.Hash160, ns, group string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("invalid address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
gKey := groupKey(ns, group)
data = storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
gsKey := groupSubjectKey(ns, group, addr)
storage.Delete(ctx, gsKey)
runtime.Notify("RemoveSubjectFromGroup", addr, ns, group)
}
func ListGroupSubjects(ns, group string) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, groupSubjectPrefix(ns, group), storage.KeysOnly|storage.RemovePrefix)
}
func DeleteGroup(ns, group string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
gKey := groupKey(ns, group)
data := storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
storage.Delete(ctx, gKey)
it := storage.Find(ctx, groupSubjectPrefix(ns, group), storage.KeysOnly)
for iterator.Next(it) {
gsKey := iterator.Value(it).([]byte)
storage.Delete(ctx, gsKey)
}
runtime.Notify("DeleteGroup", ns, group)
}
func checkContractOwner(ctx storage.Context) {
it := storage.Find(ctx, []byte{ownerKeysPrefix}, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
owner := iterator.Value(it).([]byte)
if runtime.CheckWitness(owner) {
return
}
}
panic("not witnessed")
}
func removeSubjectFromNamespace(ctx storage.Context, ns string, addr interop.Hash160) {
nsSubjKey := namespaceSubjectKey(ns, addr)
storage.Delete(ctx, nsSubjKey)
nsHash := ripemd160Hash(ns)
it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
groupHash := iterator.Value(it).([]byte)
storage.Delete(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr))
}
}
func setNamespaceSubjectName(ctx storage.Context, subj Subject) {
if subj.Name == "" {
return
}
nsSubjNameKey := namespaceSubjectNameKey(subj.Namespace, subj.Name)
subjKey := storage.Get(ctx, nsSubjNameKey).(interop.PublicKey)
if subjKey == nil {
storage.Put(ctx, nsSubjNameKey, subj.PrimaryKey)
} else if !common.BytesEqual(subjKey, subj.PrimaryKey) {
panic("subject name is not available in the current namespace")
}
}
func deleteNamespaceSubjectName(ctx storage.Context, ns, subjName string) {
if subjName == "" {
return
}
nsSubjNameKey := namespaceSubjectNameKey(ns, subjName)
storage.Delete(ctx, nsSubjNameKey)
}
func updateNamespaceSubjectName(ctx storage.Context, subj Subject, oldName string) {
if subj.Name == oldName {
return
}
deleteNamespaceSubjectName(ctx, subj.Namespace, oldName)
setNamespaceSubjectName(ctx, subj)
}
func ownerKey(owner interop.Hash160) []byte {
return append([]byte{ownerKeysPrefix}, owner...)
}
func subjectKey(key interop.PublicKey) []byte {
addr := contract.CreateStandardAccount(key)
return subjectKeyFromAddr(addr)
}
func subjectKeyFromAddr(addr interop.Hash160) []byte {
return append([]byte{subjectKeysPrefix}, addr...)
}
func subjectAdditionalKey(additionalKey interop.PublicKey, primeAddr interop.Hash160) []byte {
return append(subjectAdditionalPrefix(additionalKey), primeAddr...)
}
func subjectAdditionalPrefix(additionalKey interop.PublicKey) []byte {
addr := contract.CreateStandardAccount(additionalKey)
return append([]byte{additionalKeysPrefix}, addr...)
}
func namespaceKey(ns string) []byte {
return namespaceKeyFromHash(ripemd160Hash(ns))
}
func namespaceKeyFromHash(ns []byte) []byte {
return append([]byte{namespaceKeysPrefix}, ns...)
}
func groupKey(ns, group string) []byte {
prefix := groupPrefix(ns)
return append(prefix, ripemd160Hash(group)...)
}
func groupKeyFromHashes(nsHash, groupHash []byte) []byte {
prefix := groupPrefixFromHash(nsHash)
return append(prefix, groupHash...)
}
func groupPrefix(ns string) []byte {
return groupPrefixFromHash(ripemd160Hash(ns))
}
func groupPrefixFromHash(nsHash []byte) []byte {
return append([]byte{groupKeysPrefix}, nsHash...)
}
func ripemd160Hash(data string) []byte {
return crypto.Ripemd160([]byte(data))
}
func namespaceSubjectKey(ns string, addr interop.Hash160) []byte {
prefix := namespaceSubjectPrefix(ns)
return append(prefix, addr...)
}
func namespaceSubjectPrefix(ns string) []byte {
nsHash := ripemd160Hash(ns)
return append([]byte{namespaceSubjectsKeysPrefix}, nsHash...)
}
func namespaceSubjectNameKey(ns, subjName string) []byte {
nsHash := ripemd160Hash(ns)
nameHash := ripemd160Hash(subjName)
return append([]byte{namespaceSubjectsNamesPrefix}, append(nsHash, nameHash...)...)
}
func groupSubjectKey(ns, group string, addr interop.Hash160) []byte {
prefix := groupSubjectPrefix(ns, group)
return append(prefix, addr...)
}
func groupSubjectKeyFromHashes(nsHash, groupHash []byte, addr interop.Hash160) []byte {
prefix := groupSubjectPrefixFromHashes(nsHash, groupHash)
return append(prefix, addr...)
}
func groupSubjectPrefix(ns, group string) []byte {
nsHash := ripemd160Hash(ns)
gHash := ripemd160Hash(group)
return groupSubjectPrefixFromHashes(nsHash, gHash)
}
func groupSubjectPrefixFromHashes(nsHash, groupHash []byte) []byte {
return append([]byte{groupSubjectsKeysPrefix}, append(nsHash, groupHash...)...)
}