Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

container: allow to iterate over container list #300

Merged
merged 1 commit into from
Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion container/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: "NeoFS 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"]
Expand Down
62 changes: 46 additions & 16 deletions container/container_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -93,6 +95,26 @@ func _deploy(data interface{}, isUpdate bool) {
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There has to be some check for the current contract version here, if it's >= some this migration is not needed.

Copy link
Contributor Author

@fyrchik fyrchik Dec 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our current policy is that we remove this code after release and update "min updating from" versions in common package. This way we can update contract multiple times, but do not have complex logic in _deploy.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very user-friendly, upgrading contracts multiple times in no fun especially given that some node versions work fine with some contract versions and don't work with others.

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
}

Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
47 changes: 47 additions & 0 deletions tests/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down