[#293] container: Add IterateContainerSizes method

Add method that allows to iterate over estimation records.

Update tests to assert that list of estimations built with existing methods
is identical to estimations from iterator.

Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>
This commit is contained in:
Vladimir Domnich 2022-11-17 18:30:21 +03:00 committed by Alex Vanin
parent 1a4fa7e421
commit 03bff785d2
4 changed files with 78 additions and 17 deletions

View file

@ -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"]

View file

@ -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) {

View file

@ -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)
}
}

View file

@ -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)