forked from TrueCloudLab/frostfs-contract
Airat Arifullin
d9f523ee07
* Introduce a new method ListTargets that lists targets by kind. * Slightly fix key mapping - also concatenate kind to prefix. * Write unit-tests. * Regenerate rpcclient. Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
242 lines
6.5 KiB
Go
242 lines
6.5 KiB
Go
package policy
|
|
|
|
import (
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
|
)
|
|
|
|
// Kind represents the object the chain is attached to.
|
|
// Currently only namespace and container are supported.
|
|
type Kind byte
|
|
|
|
const (
|
|
Namespace = 'n'
|
|
Container = 'c'
|
|
IAM = 'i'
|
|
)
|
|
|
|
const (
|
|
ownerKeyPrefix = 'o'
|
|
)
|
|
|
|
const (
|
|
mappingKeyPrefix = 'm'
|
|
counterKey = "counter"
|
|
)
|
|
|
|
const (
|
|
// ErrNotAuthorized is returned when the none of the transaction signers
|
|
// belongs to the list of autorized keys.
|
|
ErrNotAuthorized = "none of the signers is authorized to change the contract"
|
|
)
|
|
|
|
// _deploy function sets up initial list of inner ring public keys.
|
|
func _deploy(data any, isUpdate bool) {
|
|
if isUpdate {
|
|
return
|
|
}
|
|
|
|
args := data.(struct {
|
|
Admin interop.Hash160
|
|
})
|
|
ctx := storage.GetContext()
|
|
if args.Admin != nil {
|
|
if len(args.Admin) != 20 {
|
|
panic("invaliad admin hash length")
|
|
}
|
|
storage.Put(ctx, []byte{ownerKeyPrefix}, args.Admin)
|
|
}
|
|
storage.Put(ctx, counterKey, 0)
|
|
}
|
|
|
|
func checkAuthorization(ctx storage.Context) {
|
|
admin := getAdmin(ctx)
|
|
if admin != nil && runtime.CheckWitness(admin) {
|
|
return
|
|
}
|
|
if runtime.CheckWitness(common.AlphabetAddress()) {
|
|
return
|
|
}
|
|
|
|
panic(ErrNotAuthorized)
|
|
}
|
|
|
|
// Version returns the version of the contract.
|
|
func Version() int {
|
|
return common.Version
|
|
}
|
|
|
|
func SetAdmin(addr interop.Hash160) {
|
|
common.CheckAlphabetWitness()
|
|
|
|
ctx := storage.GetContext()
|
|
storage.Put(ctx, []byte{ownerKeyPrefix}, addr)
|
|
}
|
|
|
|
func GetAdmin() interop.Hash160 {
|
|
ctx := storage.GetReadOnlyContext()
|
|
return getAdmin(ctx)
|
|
}
|
|
|
|
func getAdmin(ctx storage.Context) interop.Hash160 {
|
|
return storage.Get(ctx, []byte{ownerKeyPrefix}).(interop.Hash160)
|
|
}
|
|
|
|
func storageKey(prefix Kind, counter int, name []byte) []byte {
|
|
key := append([]byte{byte(prefix)}, common.ToFixedWidth64(counter)...)
|
|
return append(key, name...)
|
|
}
|
|
|
|
func mapKey(kind Kind, name []byte) []byte {
|
|
return append([]byte{mappingKeyPrefix, byte(kind)}, name...)
|
|
}
|
|
|
|
// mapToNumeric maps a name to a number. That allows to keep more space in
|
|
// a storage key shortening long names. Short entity
|
|
// names are also mapped to prevent collisions in the map.
|
|
func mapToNumeric(ctx storage.Context, kind Kind, name []byte) (mapped int, mappingExists bool) {
|
|
mKey := mapKey(kind, name)
|
|
numericID := storage.Get(ctx, mKey)
|
|
if numericID == nil {
|
|
return
|
|
}
|
|
mapped = numericID.(int)
|
|
mappingExists = true
|
|
return
|
|
}
|
|
|
|
// mapToNumericCreateIfNotExists maps a name to a number. That allows to keep
|
|
// more space in a storage key shortening long names. Short entity
|
|
// names are also mapped to prevent collisions in the map.
|
|
// If a mapping cannot be found, then the method creates and returns it.
|
|
// mapToNumericCreateIfNotExists is NOT applicable for a read-only context.
|
|
func mapToNumericCreateIfNotExists(ctx storage.Context, kind Kind, name []byte) int {
|
|
mKey := mapKey(kind, name)
|
|
numericID := storage.Get(ctx, mKey)
|
|
if numericID == nil {
|
|
counter := storage.Get(ctx, counterKey).(int)
|
|
counter++
|
|
storage.Put(ctx, counterKey, counter)
|
|
storage.Put(ctx, mKey, counter)
|
|
return counter
|
|
}
|
|
return numericID.(int)
|
|
}
|
|
|
|
func AddChain(entity Kind, entityName string, name []byte, chain []byte) {
|
|
ctx := storage.GetContext()
|
|
checkAuthorization(ctx)
|
|
|
|
entityNameBytes := mapToNumericCreateIfNotExists(ctx, entity, []byte(entityName))
|
|
key := storageKey(entity, entityNameBytes, name)
|
|
storage.Put(ctx, key, chain)
|
|
}
|
|
|
|
func GetChain(entity Kind, entityName string, name []byte) []byte {
|
|
ctx := storage.GetReadOnlyContext()
|
|
|
|
entityNameBytes, exists := mapToNumeric(ctx, entity, []byte(entityName))
|
|
if !exists {
|
|
panic("not found")
|
|
}
|
|
|
|
key := storageKey(entity, entityNameBytes, name)
|
|
data := storage.Get(ctx, key).([]byte)
|
|
if data == nil {
|
|
panic("not found")
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
func RemoveChain(entity Kind, entityName string, name []byte) {
|
|
ctx := storage.GetContext()
|
|
checkAuthorization(ctx)
|
|
|
|
entityNameNum, exists := mapToNumeric(ctx, entity, []byte(entityName))
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
key := storageKey(entity, entityNameNum, name)
|
|
storage.Delete(ctx, key)
|
|
|
|
// If no chains are left for the target, then remove the mapping.
|
|
prefix := append([]byte{byte(entity)}, common.ToFixedWidth64(entityNameNum)...)
|
|
it := storage.Find(ctx, prefix, storage.KeysOnly)
|
|
if !iterator.Next(it) {
|
|
storage.Delete(ctx, mapKey(entity, []byte(entityName)))
|
|
}
|
|
}
|
|
|
|
func RemoveChainsByPrefix(entity Kind, entityName string, name []byte) {
|
|
ctx := storage.GetContext()
|
|
checkAuthorization(ctx)
|
|
|
|
entityNameNum, exists := mapToNumeric(ctx, entity, []byte(entityName))
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
key := storageKey(entity, entityNameNum, name)
|
|
it := storage.Find(ctx, key, storage.KeysOnly)
|
|
for iterator.Next(it) {
|
|
storage.Delete(ctx, iterator.Value(it).([]byte))
|
|
}
|
|
|
|
// If no chains are left for the target, then remove the mapping.
|
|
prefix := append([]byte{byte(entity)}, common.ToFixedWidth64(entityNameNum)...)
|
|
it = storage.Find(ctx, prefix, storage.KeysOnly)
|
|
if !iterator.Next(it) {
|
|
storage.Delete(ctx, mapKey(entity, []byte(entityName)))
|
|
}
|
|
}
|
|
|
|
// ListChains lists all chains for the namespace by prefix.
|
|
// container may be empty.
|
|
func ListChains(namespace, container string, name []byte) [][]byte {
|
|
result := ListChainsByPrefix(Namespace, namespace, name)
|
|
|
|
if container != "" {
|
|
result = append(result, ListChainsByPrefix(Container, container, name)...)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// ListChainsByPrefix list all chains for the provided kind and entity by prefix.
|
|
func ListChainsByPrefix(entity Kind, entityName string, prefix []byte) [][]byte {
|
|
ctx := storage.GetReadOnlyContext()
|
|
|
|
result := [][]byte{}
|
|
|
|
entityNameBytes, exists := mapToNumeric(ctx, entity, []byte(entityName))
|
|
if !exists {
|
|
return result
|
|
}
|
|
|
|
keyPrefix := storageKey(entity, entityNameBytes, prefix)
|
|
it := storage.Find(ctx, keyPrefix, storage.ValuesOnly)
|
|
for iterator.Next(it) {
|
|
result = append(result, iterator.Value(it).([]byte))
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func IteratorChainsByPrefix(entity Kind, entityName string, prefix []byte) iterator.Iterator {
|
|
ctx := storage.GetReadOnlyContext()
|
|
id, _ := mapToNumeric(ctx, entity, []byte(entityName))
|
|
keyPrefix := storageKey(entity, id, prefix)
|
|
return storage.Find(ctx, keyPrefix, storage.ValuesOnly)
|
|
}
|
|
|
|
// ListTargets iterates over targets for which rules are defined.
|
|
func ListTargets(entity Kind) iterator.Iterator {
|
|
ctx := storage.GetReadOnlyContext()
|
|
mKey := mapKey(entity, []byte{})
|
|
return storage.Find(ctx, mKey, storage.KeysOnly|storage.RemovePrefix)
|
|
}
|