[#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" name: "FrostFS Container"
safemethods: ["count", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "version"] safemethods: ["count", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "iterateContainerSizes", "version"]
permissions: permissions:
- methods: ["update", "addKey", "transferX", - methods: ["update", "addKey", "transferX",
"register", "addRecord", "deleteRecords"] "register", "addRecord", "deleteRecords"]

View file

@ -551,7 +551,7 @@ func GetContainerSize(id []byte) containerSizes {
} }
// ListContainerSizes method returns the IDs of container size estimations // 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 { func ListContainerSizes(epoch int) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -582,6 +582,19 @@ func ListContainerSizes(epoch int) [][]byte {
return result 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 // 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. // epochNum + 3. It can be invoked only by NewEpoch method of the Netmap contract.
func NewEpoch(epochNum int) { func NewEpoch(epochNum int) {

View file

@ -3,6 +3,7 @@ package tests
import ( import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"math/big"
"path" "path"
"testing" "testing"
@ -10,6 +11,7 @@ import (
"github.com/TrueCloudLab/frostfs-contract/container" "github.com/TrueCloudLab/frostfs-contract/container"
"github.com/TrueCloudLab/frostfs-contract/nns" "github.com/TrueCloudLab/frostfs-contract/nns"
"github.com/mr-tron/base58" "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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util" "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) { 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) s, err := c.TestInvoke(t, "listContainerSizes", epoch)
require.NoError(t, err) require.NoError(t, err)
@ -375,9 +387,8 @@ func checkEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt
item := s.Top().Item() item := s.Top().Item()
switch it := item.(type) { switch it := item.(type) {
case stackitem.Null: case stackitem.Null:
require.Equal(t, 0, len(estimations))
require.Equal(t, stackitem.Null{}, it) require.Equal(t, stackitem.Null{}, it)
return return make([]estimation, 0)
case *stackitem.Array: case *stackitem.Array:
id, err = it.Value().([]stackitem.Item)[0].TryBytes() id, err = it.Value().([]stackitem.Item)[0].TryBytes()
require.NoError(t, err) 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) s, err = c.TestInvoke(t, "getContainerSize", id)
require.NoError(t, err) require.NoError(t, err)
// Here and below we assume that all estimations in the contract are related to our container
sizes := s.Top().Array() sizes := s.Top().Array()
require.Equal(t, cnt.id[:], sizes[0].Value()) require.Equal(t, cnt.id[:], sizes[0].Value())
actual := sizes[1].Value().([]stackitem.Item) return convertStackToEstimations(sizes[1].Value().([]stackitem.Item))
require.Equal(t, len(estimations), len(actual)) }
for i := range actual {
// type estimation struct { func getIterEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64) []estimation {
// from interop.PublicKey iterStack, err := c.TestInvoke(t, "iterateContainerSizes", epoch)
// size int require.NoError(t, err)
// } iter := iterStack.Pop().Value().(*storage.Iterator)
est := actual[i].Value().([]stackitem.Item)
pub := est[0].Value().([]byte) // 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 found := false
for i := range estimations { for _, a := range actual {
if found = bytes.Equal(estimations[i].from, pub); found { if found = bytes.Equal(e.from, a.from); found {
require.Equal(t, stackitem.Make(estimations[i].size), est[1]) require.Equal(t, e.size, a.size)
break 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 ( import (
"testing" "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"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain" "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 { func newExecutor(t *testing.T) *neotest.Executor {
bc, acc := chain.NewSingle(t) bc, acc := chain.NewSingle(t)
return neotest.NewExecutor(t, bc, acc, acc) return neotest.NewExecutor(t, bc, acc, acc)