From e0f6fe3bc9e9353d2f5fa55ad0437b8971ece20f Mon Sep 17 00:00:00 2001 From: Airat Arifullin Date: Thu, 18 Jan 2024 10:46:19 +0300 Subject: [PATCH] [#61] policy: Shorten policy contract keys * Map long entity names to 8-bytes long numbers. * Add desctiption to docs. Signed-off-by: Airat Arifullin --- policy/doc.go | 2 ++ policy/policy_contract.go | 76 ++++++++++++++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/policy/doc.go b/policy/doc.go index dee664a..bfde740 100644 --- a/policy/doc.go +++ b/policy/doc.go @@ -6,6 +6,8 @@ |------------------------------------------|--------|-----------------------------------| | 'c' + uint16(len(container)) + container | []byte | Namespace chain | | 'n' + uint16(len(namespace)) + namespace | []byte | Container chain | + | 'm' + entity name (namespace/container) | []byte | Mapped name to an encoded number | + | 'counter' | uint64 | Integer counter used for mapping | */ diff --git a/policy/policy_contract.go b/policy/policy_contract.go index a3dd976..3a44153 100644 --- a/policy/policy_contract.go +++ b/policy/policy_contract.go @@ -22,6 +22,11 @@ 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. @@ -44,6 +49,7 @@ func _deploy(data any, isUpdate bool) { } storage.Put(ctx, []byte{ownerKeyPrefix}, args.Admin) } + storage.Put(ctx, counterKey, 0) } func checkAuthorization(ctx storage.Context) { @@ -79,24 +85,61 @@ func getAdmin(ctx storage.Context) interop.Hash160 { return storage.Get(ctx, []byte{ownerKeyPrefix}).(interop.Hash160) } -func storageKey(prefix Kind, entityName string, name []byte) []byte { - ln := len(entityName) - key := append([]byte{byte(prefix)}, byte(ln&0xFF), byte(ln>>8)) - key = append(key, entityName...) +func storageKey(prefix Kind, counter int, name []byte) []byte { + key := append([]byte{byte(prefix)}, common.ToFixedWidth64(counter)...) return append(key, 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, name []byte) (mapped int, mappingExists bool) { + mKey := append([]byte{mappingKeyPrefix}, 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, name []byte) int { + mKey := append([]byte{mappingKeyPrefix}, 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) - key := storageKey(entity, entityName, name) + entityNameBytes := mapToNumericCreateIfNotExists(ctx, []byte(entityName)) + key := storageKey(entity, entityNameBytes, name) storage.Put(ctx, key, chain) } func GetChain(entity Kind, entityName string, name []byte) []byte { ctx := storage.GetReadOnlyContext() - key := storageKey(entity, entityName, name) + + entityNameBytes, exists := mapToNumeric(ctx, []byte(entityName)) + if !exists { + panic("not found") + } + + key := storageKey(entity, entityNameBytes, name) data := storage.Get(ctx, key).([]byte) if data == nil { panic("not found") @@ -109,7 +152,12 @@ func RemoveChain(entity Kind, entityName string, name []byte) { ctx := storage.GetContext() checkAuthorization(ctx) - key := storageKey(entity, entityName, name) + entityNameBytes, exists := mapToNumeric(ctx, []byte(entityName)) + if !exists { + return + } + + key := storageKey(entity, entityNameBytes, name) storage.Delete(ctx, key) } @@ -117,7 +165,12 @@ func RemoveChainsByPrefix(entity Kind, entityName string, name []byte) { ctx := storage.GetContext() checkAuthorization(ctx) - key := storageKey(entity, entityName, name) + entityNameBytes, exists := mapToNumeric(ctx, []byte(entityName)) + if !exists { + return + } + + key := storageKey(entity, entityNameBytes, name) it := storage.Find(ctx, key, storage.KeysOnly) for iterator.Next(it) { storage.Delete(ctx, iterator.Value(it).([]byte)) @@ -142,7 +195,12 @@ func ListChainsByPrefix(entity Kind, entityName string, prefix []byte) [][]byte result := [][]byte{} - keyPrefix := storageKey(entity, entityName, prefix) + entityNameBytes, exists := mapToNumeric(ctx, []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))