policy: Shorten policy contract keys #71
|
@ -6,6 +6,8 @@
|
||||||
|------------------------------------------|--------|-----------------------------------|
|
|------------------------------------------|--------|-----------------------------------|
|
||||||
| 'c' + uint16(len(container)) + container | []byte | Namespace chain |
|
| 'c' + uint16(len(container)) + container | []byte | Namespace chain |
|
||||||
| 'n' + uint16(len(namespace)) + namespace | []byte | Container 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 |
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,11 @@ const (
|
||||||
ownerKeyPrefix = 'o'
|
ownerKeyPrefix = 'o'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mappingKeyPrefix = 'm'
|
||||||
|
counterKey = "counter"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ErrNotAuthorized is returned when the none of the transaction signers
|
// ErrNotAuthorized is returned when the none of the transaction signers
|
||||||
// belongs to the list of autorized keys.
|
// 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, []byte{ownerKeyPrefix}, args.Admin)
|
||||||
}
|
}
|
||||||
|
storage.Put(ctx, counterKey, 0)
|
||||||
}
|
}
|
||||||
dkirillov marked this conversation as resolved
Outdated
|
|||||||
|
|
||||||
func checkAuthorization(ctx storage.Context) {
|
func checkAuthorization(ctx storage.Context) {
|
||||||
|
@ -79,24 +85,61 @@ func getAdmin(ctx storage.Context) interop.Hash160 {
|
||||||
return storage.Get(ctx, []byte{ownerKeyPrefix}).(interop.Hash160)
|
return storage.Get(ctx, []byte{ownerKeyPrefix}).(interop.Hash160)
|
||||||
}
|
}
|
||||||
|
|
||||||
func storageKey(prefix Kind, entityName string, name []byte) []byte {
|
func storageKey(prefix Kind, counter int, name []byte) []byte {
|
||||||
fyrchik
commented
Why not calculate this inside Why not calculate this inside `storageKey()`
The invariant is hard to enforce.
Otherwise, let's rename `entityName` parameter, it is `counter` now?
fyrchik
commented
`mapToNumeric` could return `int`, this would solve all mentioned problems.
aarifullin
commented
You've asked
Because: This method is also invoked by Fusing these methods would look like that:
Are we okay with that? You've asked
> Why not calculate this inside storageKey()
Because:
This method is also invoked by `GetChain` that yields read-only context. If we consider your suggestion, then it leads to a situation when we need to make `storageKey` to determine whether it can create mapping or not, i.e. write to the storage or not (`storage.Put`) -> we need to pass two more params: `ctx` and `isReadOnlyCtx`: actually, I tried to avoid this
Fusing these methods would look like that:
```go
func storageKeyAlt(ctx storage.Context, isReadOnlyCtx bool, prefix Kind, entityName string, name []byte) []byte {
mKey := append([]byte{mappingKeyPrefix}, entityName...)
var mapped int
numericID := storage.Get(ctx, mKey)
if numericID == nil {
if isReadOnlyCtx {
return nil
}
counter := storage.Get(ctx, counterKey).(int)
counter++
storage.Put(ctx, counterKey, counter)
storage.Put(ctx, mKey, counter)
mapped = counter
} else {
mapped = numericID.(int)
}
key := append([]byte{byte(prefix)}, common.ToFixedWidth64(mapped)...)
return append(key, name...)
}
```
Are we okay with that?
fyrchik
commented
Doesn't look nice either, but we could just move Doesn't look nice either, but we could just move `common.ToFixedWidth64`invocation: so that `storageKey` accepts `int` returned from `mapToNumeric`.
aarifullin
commented
Alright. Since mapping methods return Alright. Since mapping methods return `int` and `storageKey` recieves `int`
|
|||||||
ln := len(entityName)
|
key := append([]byte{byte(prefix)}, common.ToFixedWidth64(counter)...)
|
||||||
key := append([]byte{byte(prefix)}, byte(ln&0xFF), byte(ln>>8))
|
|
||||||
key = append(key, entityName...)
|
|
||||||
return append(key, name...)
|
return append(key, name...)
|
||||||
}
|
}
|
||||||
dkirillov marked this conversation as resolved
dkirillov
commented
Shouldn't we simplify this to Shouldn't we simplify this to `key := append([]byte{byte(prefix)}, entityName...)`
aarifullin
commented
Appending Appending `entityName`'s length wasn't idea of mine (it has been introduced by @fyrchik), but I think it is fine because it helps to determine where a chain ID starts in a storage key.
dkirillov
commented
`entityName` now always 8 bytes long. So we can save two bytes here if I understand it correctly
aarifullin
commented
Alright. I've removed this append Alright. I've removed this append
|
|||||||
|
|
||||||
|
// 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
|
||||||
dkirillov marked this conversation as resolved
Outdated
dkirillov
commented
I'm not sure that we can use plain I'm not sure that we can use plain `namespace` if its length is less than 8 bytes because it seems we can conflict with mapped one.
Let's consider namespace with name `a`, it's []byte representation is `[]byte{97}`, but we will get the same representation for 97 container that length is greater 8 as I can undersand
aarifullin
commented
You're absolutely right. I haven't considered this collision. Alright, I'll remove this out then You're absolutely right. I haven't considered this collision. Alright, I'll remove this out then
aarifullin
commented
Fixed. Since all names are mapped to numbers Fixed. Since **all** names are mapped to numbers
|
|||||||
|
}
|
||||||
|
|
||||||
|
// mapToNumericCreateIfNotExists maps a name to a number. That allows to keep
|
||||||
dkirillov marked this conversation as resolved
Outdated
dkirillov
commented
Can be just Can be just `return common.ToFixedWidth64(mapped)`
aarifullin
commented
Fixed Fixed
|
|||||||
|
// 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) {
|
func AddChain(entity Kind, entityName string, name []byte, chain []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
checkAuthorization(ctx)
|
checkAuthorization(ctx)
|
||||||
|
|
||||||
key := storageKey(entity, entityName, name)
|
entityNameBytes := mapToNumericCreateIfNotExists(ctx, []byte(entityName))
|
||||||
|
key := storageKey(entity, entityNameBytes, name)
|
||||||
storage.Put(ctx, key, chain)
|
storage.Put(ctx, key, chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetChain(entity Kind, entityName string, name []byte) []byte {
|
func GetChain(entity Kind, entityName string, name []byte) []byte {
|
||||||
ctx := storage.GetReadOnlyContext()
|
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)
|
data := storage.Get(ctx, key).([]byte)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
panic("not found")
|
panic("not found")
|
||||||
|
@ -109,7 +152,12 @@ func RemoveChain(entity Kind, entityName string, name []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
checkAuthorization(ctx)
|
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)
|
storage.Delete(ctx, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +165,12 @@ func RemoveChainsByPrefix(entity Kind, entityName string, name []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
checkAuthorization(ctx)
|
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)
|
it := storage.Find(ctx, key, storage.KeysOnly)
|
||||||
for iterator.Next(it) {
|
for iterator.Next(it) {
|
||||||
storage.Delete(ctx, iterator.Value(it).([]byte))
|
storage.Delete(ctx, iterator.Value(it).([]byte))
|
||||||
|
@ -142,7 +195,12 @@ func ListChainsByPrefix(entity Kind, entityName string, prefix []byte) [][]byte
|
||||||
|
|
||||||
result := [][]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)
|
it := storage.Find(ctx, keyPrefix, storage.ValuesOnly)
|
||||||
for iterator.Next(it) {
|
for iterator.Next(it) {
|
||||||
result = append(result, iterator.Value(it).([]byte))
|
result = append(result, iterator.Value(it).([]byte))
|
||||||
|
|
Isn't
int
in contracts isuint64
anyway and we can skip conversion?I think you're right. I have replaced
uint64
byint