diff --git a/container/config.yml b/container/config.yml index b051403..682b272 100644 --- a/container/config.yml +++ b/container/config.yml @@ -1,5 +1,5 @@ name: "FrostFS Container" -safemethods: ["count", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "iterateContainerSizes", "version"] +safemethods: ["count", "containersOf", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "iterateContainerSizes", "version"] permissions: - methods: ["update", "addKey", "transferX", "register", "addRecord", "deleteRecords"] diff --git a/container/container_contract.go b/container/container_contract.go index f08c1d1..c7f2077 100644 --- a/container/container_contract.go +++ b/container/container_contract.go @@ -62,6 +62,8 @@ const ( singleEstimatePrefix = "est" estimateKeyPrefix = "cnr" + containerKeyPrefix = 'x' + ownerKeyPrefix = 'o' estimatePostfixSize = 10 // CleanupDelta contains the number of the last epochs for which container estimations are present. CleanupDelta = 3 @@ -93,6 +95,26 @@ func _deploy(data interface{}, isUpdate bool) { if isUpdate { args := data.([]interface{}) common.CheckVersion(args[len(args)-1].(int)) + + it := storage.Find(ctx, []byte{}, storage.None) + for iterator.Next(it) { + item := iterator.Value(it).(struct { + key []byte + value []byte + }) + + // Migrate container. + if len(item.key) == containerIDSize { + storage.Delete(ctx, item.key) + storage.Put(ctx, append([]byte{containerKeyPrefix}, item.key...), item.value) + } + + // Migrate owner-cid map. + if len(item.key) == 25 /* owner id size */ +containerIDSize { + storage.Delete(ctx, item.key) + storage.Put(ctx, append([]byte{ownerKeyPrefix}, item.key...), item.value) + } + } return } @@ -391,17 +413,24 @@ func Owner(containerID []byte) []byte { func Count() int { count := 0 ctx := storage.GetReadOnlyContext() - it := storage.Find(ctx, []byte{}, storage.KeysOnly) + it := storage.Find(ctx, []byte{containerKeyPrefix}, storage.KeysOnly) for iterator.Next(it) { - key := iterator.Value(it).([]byte) - // V2 format - if len(key) == containerIDSize { - count++ - } + count++ } return count } +// ContainersOf iterates over all container IDs owned by the specified owner. +// If owner is nil, it iterates over all containers. +func ContainersOf(owner []byte) iterator.Iterator { + ctx := storage.GetReadOnlyContext() + key := []byte{ownerKeyPrefix} + if len(owner) != 0 { + key = append(key, owner...) + } + return storage.Find(ctx, key, storage.ValuesOnly) +} + // List method returns a list of all container IDs owned by the specified owner. func List(owner []byte) [][]byte { ctx := storage.GetReadOnlyContext() @@ -412,7 +441,7 @@ func List(owner []byte) [][]byte { var list [][]byte - it := storage.Find(ctx, owner, storage.ValuesOnly) + it := storage.Find(ctx, append([]byte{ownerKeyPrefix}, owner...), storage.ValuesOnly) for iterator.Next(it) { id := iterator.Value(it).([]byte) list = append(list, id) @@ -700,29 +729,30 @@ func Version() int { } func addContainer(ctx storage.Context, id, owner []byte, container Container) { - containerListKey := append(owner, id...) + containerListKey := append([]byte{ownerKeyPrefix}, owner...) + containerListKey = append(containerListKey, id...) storage.Put(ctx, containerListKey, id) - common.SetSerialized(ctx, id, container) + idKey := append([]byte{containerKeyPrefix}, id...) + common.SetSerialized(ctx, idKey, container) } func removeContainer(ctx storage.Context, id []byte, owner []byte) { - containerListKey := append(owner, id...) + containerListKey := append([]byte{ownerKeyPrefix}, owner...) + containerListKey = append(containerListKey, id...) storage.Delete(ctx, containerListKey) - storage.Delete(ctx, id) + storage.Delete(ctx, append([]byte{containerKeyPrefix}, id...)) } func getAllContainers(ctx storage.Context) [][]byte { var list [][]byte - it := storage.Find(ctx, []byte{}, storage.KeysOnly) + it := storage.Find(ctx, []byte{containerKeyPrefix}, storage.KeysOnly|storage.RemovePrefix) for iterator.Next(it) { key := iterator.Value(it).([]byte) // it MUST BE `storage.KeysOnly` // V2 format - if len(key) == containerIDSize { - list = append(list, key) - } + list = append(list, key) } return list @@ -739,7 +769,7 @@ func getEACL(ctx storage.Context, cid []byte) ExtendedACL { } func getContainer(ctx storage.Context, cid []byte) Container { - data := storage.Get(ctx, cid) + data := storage.Get(ctx, append([]byte{containerKeyPrefix}, cid...)) if data != nil { return std.Deserialize(data.([]byte)).(Container) } diff --git a/tests/container_test.go b/tests/container_test.go index 3cc24d1..c71b4ab 100644 --- a/tests/container_test.go +++ b/tests/container_test.go @@ -103,15 +103,62 @@ func TestContainerCount(t *testing.T) { cnt3 := dummyContainer(acc1) balanceMint(t, cBal, acc1, containerFee*1, []byte{}) c.Invoke(t, stackitem.Null{}, "put", cnt3.value, cnt3.sig, cnt3.pub, cnt3.token) + checkContainerList(t, c, [][]byte{cnt1.id[:], cnt2.id[:], cnt3.id[:]}) c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.token) checkCount(t, 2) + checkContainerList(t, c, [][]byte{cnt2.id[:], cnt3.id[:]}) c.Invoke(t, stackitem.Null{}, "delete", cnt2.id[:], cnt2.sig, cnt2.token) checkCount(t, 1) + checkContainerList(t, c, [][]byte{cnt3.id[:]}) c.Invoke(t, stackitem.Null{}, "delete", cnt3.id[:], cnt3.sig, cnt3.token) checkCount(t, 0) + checkContainerList(t, c, [][]byte{}) +} + +func checkContainerList(t *testing.T, c *neotest.ContractInvoker, expected [][]byte) { + t.Run("check with `list`", func(t *testing.T) { + s, err := c.TestInvoke(t, "list", nil) + require.NoError(t, err) + require.Equal(t, 1, s.Len()) + + if len(expected) == 0 { + _, ok := s.Top().Item().(stackitem.Null) + require.True(t, ok) + return + } + + arr, ok := s.Top().Value().([]stackitem.Item) + require.True(t, ok) + require.Equal(t, len(expected), len(arr)) + + actual := make([][]byte, 0, len(expected)) + for i := range arr { + id, ok := arr[i].Value().([]byte) + require.True(t, ok) + actual = append(actual, id) + } + require.ElementsMatch(t, expected, actual) + }) + t.Run("check with `containersOf`", func(t *testing.T) { + s, err := c.TestInvoke(t, "containersOf", nil) + require.NoError(t, err) + require.Equal(t, 1, s.Len()) + + iter, ok := s.Top().Value().(*storage.Iterator) + require.True(t, ok) + + actual := make([][]byte, 0, len(expected)) + for iter.Next() { + id, ok := iter.Value().Value().([]byte) + require.True(t, ok) + actual = append(actual, id) + } + require.ElementsMatch(t, expected, actual) + }) + } func TestContainerPut(t *testing.T) {