diff --git a/container/config.yml b/container/config.yml index dfe3e65..b051403 100644 --- a/container/config.yml +++ b/container/config.yml @@ -1,5 +1,5 @@ name: "FrostFS Container" -safemethods: ["count", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "version"] +safemethods: ["count", "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 85396c3..f08c1d1 100644 --- a/container/container_contract.go +++ b/container/container_contract.go @@ -551,7 +551,7 @@ func GetContainerSize(id []byte) containerSizes { } // ListContainerSizes method returns the IDs of container size estimations -// that has been registered for the specified epoch. +// that have been registered for the specified epoch. func ListContainerSizes(epoch int) [][]byte { ctx := storage.GetReadOnlyContext() @@ -582,6 +582,19 @@ func ListContainerSizes(epoch int) [][]byte { return result } +// IterateContainerSizes method returns iterator over container size estimations +// that have been registered for the specified epoch. +func IterateContainerSizes(epoch int) iterator.Iterator { + ctx := storage.GetReadOnlyContext() + + var buf interface{} = epoch + + key := []byte(estimateKeyPrefix) + key = append(key, buf.([]byte)...) + + return storage.Find(ctx, key, storage.DeserializeValues) +} + // NewEpoch method removes all container size estimations from epoch older than // epochNum + 3. It can be invoked only by NewEpoch method of the Netmap contract. func NewEpoch(epochNum int) { diff --git a/tests/container_test.go b/tests/container_test.go index 56d7cef..3cc24d1 100644 --- a/tests/container_test.go +++ b/tests/container_test.go @@ -3,6 +3,7 @@ package tests import ( "bytes" "crypto/sha256" + "math/big" "path" "testing" @@ -10,6 +11,7 @@ import ( "github.com/TrueCloudLab/frostfs-contract/container" "github.com/TrueCloudLab/frostfs-contract/nns" "github.com/mr-tron/base58" + "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/util" @@ -366,6 +368,16 @@ type estimation struct { } func checkEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt testContainer, estimations ...estimation) { + // Check that listed estimations match expected + listEstimations := getListEstimations(t, c, epoch, cnt) + requireEstimationsMatch(t, estimations, listEstimations) + + // Check that iterated estimations match expected + iterEstimations := getIterEstimations(t, c, epoch) + requireEstimationsMatch(t, estimations, iterEstimations) +} + +func getListEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt testContainer) []estimation { s, err := c.TestInvoke(t, "listContainerSizes", epoch) require.NoError(t, err) @@ -375,9 +387,8 @@ func checkEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt item := s.Top().Item() switch it := item.(type) { case stackitem.Null: - require.Equal(t, 0, len(estimations)) require.Equal(t, stackitem.Null{}, it) - return + return make([]estimation, 0) case *stackitem.Array: id, err = it.Value().([]stackitem.Item)[0].TryBytes() require.NoError(t, err) @@ -388,25 +399,52 @@ func checkEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt s, err = c.TestInvoke(t, "getContainerSize", id) require.NoError(t, err) + // Here and below we assume that all estimations in the contract are related to our container sizes := s.Top().Array() require.Equal(t, cnt.id[:], sizes[0].Value()) - actual := sizes[1].Value().([]stackitem.Item) - require.Equal(t, len(estimations), len(actual)) - for i := range actual { - // type estimation struct { - // from interop.PublicKey - // size int - // } - est := actual[i].Value().([]stackitem.Item) - pub := est[0].Value().([]byte) + return convertStackToEstimations(sizes[1].Value().([]stackitem.Item)) +} + +func getIterEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64) []estimation { + iterStack, err := c.TestInvoke(t, "iterateContainerSizes", epoch) + require.NoError(t, err) + iter := iterStack.Pop().Value().(*storage.Iterator) + + // Iterator contains pairs: key + estimation (as stack item), we extract estimations only + pairs := iteratorToArray(iter) + estimationItems := make([]stackitem.Item, len(pairs)) + for i, pair := range pairs { + pairItems := pair.Value().([]stackitem.Item) + estimationItems[i] = pairItems[1] + } + + return convertStackToEstimations(estimationItems) +} + +func convertStackToEstimations(stackItems []stackitem.Item) []estimation { + estimations := make([]estimation, 0, len(stackItems)) + for _, item := range stackItems { + value := item.Value().([]stackitem.Item) + from := value[0].Value().([]byte) + size := value[1].Value().(*big.Int) + + estimation := estimation{from: from, size: size.Int64()} + estimations = append(estimations, estimation) + } + return estimations +} + +func requireEstimationsMatch(t *testing.T, expected []estimation, actual []estimation) { + require.Equal(t, len(expected), len(actual)) + for _, e := range expected { found := false - for i := range estimations { - if found = bytes.Equal(estimations[i].from, pub); found { - require.Equal(t, stackitem.Make(estimations[i].size), est[1]) + for _, a := range actual { + if found = bytes.Equal(e.from, a.from); found { + require.Equal(t, e.size, a.size) break } } - require.True(t, found, "expected estimation from %x to be present", pub) + require.True(t, found, "expected estimation from %x to be present", e.from) } } diff --git a/tests/util.go b/tests/util.go index 1e2958d..48f5f5d 100644 --- a/tests/util.go +++ b/tests/util.go @@ -3,10 +3,20 @@ package tests import ( "testing" + "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) +func iteratorToArray(iter *storage.Iterator) []stackitem.Item { + stackItems := make([]stackitem.Item, 0) + for iter.Next() { + stackItems = append(stackItems, iter.Value()) + } + return stackItems +} + func newExecutor(t *testing.T) *neotest.Executor { bc, acc := chain.NewSingle(t) return neotest.NewExecutor(t, bc, acc, acc)