[#215] container/tests: add tests for container size estimation

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2022-01-17 13:51:11 +03:00 committed by Alex Vanin
parent 53f102344f
commit 7bca6bf782
3 changed files with 147 additions and 21 deletions

View file

@ -61,7 +61,8 @@ const (
estimateKeyPrefix = "cnr" estimateKeyPrefix = "cnr"
estimatePostfixSize = 10 estimatePostfixSize = 10
cleanupDelta = 3 // CleanupDelta contains number last epochs for which container estimations are present.
CleanupDelta = 3
// NotFoundError is returned if container is missing. // NotFoundError is returned if container is missing.
NotFoundError = "container does not exist" NotFoundError = "container does not exist"
@ -780,7 +781,7 @@ func cleanupContainers(ctx storage.Context, epoch int) {
var n interface{} = nbytes var n interface{} = nbytes
if epoch-n.(int) > cleanupDelta { if epoch-n.(int) > CleanupDelta {
storage.Delete(ctx, k) storage.Delete(ctx, k)
} }
} }

View file

@ -1,6 +1,7 @@
package tests package tests
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"path" "path"
"testing" "testing"
@ -13,6 +14,7 @@ import (
"github.com/nspcc-dev/neofs-contract/common" "github.com/nspcc-dev/neofs-contract/common"
"github.com/nspcc-dev/neofs-contract/container" "github.com/nspcc-dev/neofs-contract/container"
"github.com/nspcc-dev/neofs-contract/nns" "github.com/nspcc-dev/neofs-contract/nns"
"github.com/stretchr/testify/require"
) )
const containerPath = "../container" const containerPath = "../container"
@ -36,7 +38,7 @@ func deployContainerContract(t *testing.T, e *neotest.Executor, addrNetmap, addr
return c.Hash return c.Hash
} }
func newContainerInvoker(t *testing.T) (*neotest.ContractInvoker, *neotest.ContractInvoker) { func newContainerInvoker(t *testing.T) (*neotest.ContractInvoker, *neotest.ContractInvoker, *neotest.ContractInvoker) {
e := newExecutor(t) e := newExecutor(t)
ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml")) ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
@ -50,7 +52,7 @@ func newContainerInvoker(t *testing.T) (*neotest.ContractInvoker, *neotest.Contr
container.AliasFeeKey, int64(containerAliasFee)) container.AliasFeeKey, int64(containerAliasFee))
deployBalanceContract(t, e, ctrNetmap.Hash, ctrContainer.Hash) deployBalanceContract(t, e, ctrNetmap.Hash, ctrContainer.Hash)
deployContainerContract(t, e, ctrNetmap.Hash, ctrBalance.Hash, ctrNNS.Hash) deployContainerContract(t, e, ctrNetmap.Hash, ctrBalance.Hash, ctrNNS.Hash)
return e.CommitteeInvoker(ctrContainer.Hash), e.CommitteeInvoker(ctrBalance.Hash) return e.CommitteeInvoker(ctrContainer.Hash), e.CommitteeInvoker(ctrBalance.Hash), e.CommitteeInvoker(ctrNetmap.Hash)
} }
func setContainerOwner(c []byte, acc neotest.Signer) { func setContainerOwner(c []byte, acc neotest.Signer) {
@ -78,7 +80,7 @@ func dummyContainer(owner neotest.Signer) testContainer {
} }
func TestContainerPut(t *testing.T) { func TestContainerPut(t *testing.T) {
c, cBal := newContainerInvoker(t) c, cBal, _ := newContainerInvoker(t)
acc := c.NewAccount(t) acc := c.NewAccount(t)
cnt := dummyContainer(acc) cnt := dummyContainer(acc)
@ -155,7 +157,7 @@ func addContainer(t *testing.T, c, cBal *neotest.ContractInvoker) (neotest.Signe
} }
func TestContainerDelete(t *testing.T) { func TestContainerDelete(t *testing.T) {
c, cBal := newContainerInvoker(t) c, cBal, _ := newContainerInvoker(t)
acc, cnt := addContainer(t, c, cBal) acc, cnt := addContainer(t, c, cBal)
cAcc := c.WithSigners(acc) cAcc := c.WithSigners(acc)
@ -174,7 +176,7 @@ func TestContainerDelete(t *testing.T) {
} }
func TestContainerOwner(t *testing.T) { func TestContainerOwner(t *testing.T) {
c, cBal := newContainerInvoker(t) c, cBal, _ := newContainerInvoker(t)
acc, cnt := addContainer(t, c, cBal) acc, cnt := addContainer(t, c, cBal)
@ -189,7 +191,7 @@ func TestContainerOwner(t *testing.T) {
} }
func TestContainerGet(t *testing.T) { func TestContainerGet(t *testing.T) {
c, cBal := newContainerInvoker(t) c, cBal, _ := newContainerInvoker(t)
_, cnt := addContainer(t, c, cBal) _, cnt := addContainer(t, c, cBal)
@ -227,7 +229,7 @@ func dummyEACL(containerID [32]byte) eacl {
} }
func TestContainerSetEACL(t *testing.T) { func TestContainerSetEACL(t *testing.T) {
c, cBal := newContainerInvoker(t) c, cBal, _ := newContainerInvoker(t)
acc, cnt := addContainer(t, c, cBal) acc, cnt := addContainer(t, c, cBal)
@ -253,3 +255,111 @@ func TestContainerSetEACL(t *testing.T) {
}) })
c.Invoke(t, expected, "eACL", cnt.id[:]) c.Invoke(t, expected, "eACL", cnt.id[:])
} }
func TestContainerSizeEstimation(t *testing.T) {
c, cBal, cNm := newContainerInvoker(t)
_, cnt := addContainer(t, c, cBal)
nodes := []testNodeInfo{
newStorageNode(t, c),
newStorageNode(t, c),
newStorageNode(t, c),
}
for i := range nodes {
cNm.WithSigners(nodes[i].signer).Invoke(t, stackitem.Null{}, "addPeer", nodes[i].raw)
cNm.Invoke(t, stackitem.Null{}, "register", nodes[i].raw)
}
// putContainerSize retrieves storage nodes from the previous snapshot,
// so epoch must be incremented twice.
cNm.Invoke(t, stackitem.Null{}, "newEpoch", int64(1))
cNm.Invoke(t, stackitem.Null{}, "newEpoch", int64(2))
t.Run("must be witnessed by key in the argument", func(t *testing.T) {
c.WithSigners(nodes[1].signer).InvokeFail(t, common.ErrWitnessFailed, "putContainerSize",
int64(2), cnt.id[:], int64(123), nodes[0].pub)
})
c.WithSigners(nodes[0].signer).Invoke(t, stackitem.Null{}, "putContainerSize",
int64(2), cnt.id[:], int64(123), nodes[0].pub)
estimations := []estimation{{nodes[0].pub, 123}}
checkEstimations(t, c, 2, cnt, estimations...)
c.WithSigners(nodes[1].signer).Invoke(t, stackitem.Null{}, "putContainerSize",
int64(2), cnt.id[:], int64(42), nodes[1].pub)
estimations = append(estimations, estimation{nodes[1].pub, int64(42)})
checkEstimations(t, c, 2, cnt, estimations...)
t.Run("add estimation for a different epoch", func(t *testing.T) {
c.WithSigners(nodes[2].signer).Invoke(t, stackitem.Null{}, "putContainerSize",
int64(1), cnt.id[:], int64(777), nodes[2].pub)
checkEstimations(t, c, 1, cnt, estimation{nodes[2].pub, 777})
checkEstimations(t, c, 2, cnt, estimations...)
})
c.WithSigners(nodes[2].signer).Invoke(t, stackitem.Null{}, "putContainerSize",
int64(3), cnt.id[:], int64(888), nodes[2].pub)
checkEstimations(t, c, 3, cnt, estimation{nodes[2].pub, 888})
// Remove old estimations.
for i := int64(1); i <= container.CleanupDelta; i++ {
cNm.Invoke(t, stackitem.Null{}, "newEpoch", 2+i)
checkEstimations(t, c, 2, cnt, estimations...)
checkEstimations(t, c, 3, cnt, estimation{nodes[2].pub, 888})
}
cNm.Invoke(t, stackitem.Null{}, "newEpoch", int64(2+container.CleanupDelta+1))
checkEstimations(t, c, 2, cnt)
checkEstimations(t, c, 3, cnt, estimation{nodes[2].pub, 888})
}
type estimation struct {
from []byte
size int64
}
func checkEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt testContainer, estimations ...estimation) {
s, err := c.TestInvoke(t, "listContainerSizes", epoch)
require.NoError(t, err)
var id []byte
// When there are no estimations, listContainerSizes can also return nothing.
item := s.Top().Item()
switch it := item.(type) {
case stackitem.Null:
require.Equal(t, 0, len(estimations))
require.Equal(t, stackitem.Null{}, it)
return
case *stackitem.Array:
id, err = it.Value().([]stackitem.Item)[0].TryBytes()
require.NoError(t, err)
default:
require.FailNow(t, "invalid return type for listContainerSizes")
}
s, err = c.TestInvoke(t, "getContainerSize", id)
require.NoError(t, err)
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)
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])
break
}
}
require.True(t, found, "expected estimation from %x to be present", pub)
}
}

View file

@ -56,13 +56,28 @@ func TestDeploySetConfig(t *testing.T) {
"config", container.AliasFeeKey) "config", container.AliasFeeKey)
} }
func dummyNodeInfo(acc neotest.Signer) []byte { type testNodeInfo struct {
signer neotest.SingleSigner
pub []byte
raw []byte
}
func dummyNodeInfo(acc neotest.Signer) testNodeInfo {
ni := make([]byte, 66) ni := make([]byte, 66)
rand.Read(ni) rand.Read(ni)
pub, _ := vm.ParseSignatureContract(acc.Script()) s := acc.(neotest.SingleSigner)
pub := s.Account().PrivateKey().PublicKey().Bytes()
copy(ni[2:], pub) copy(ni[2:], pub)
return ni return testNodeInfo{
signer: s,
pub: pub,
raw: ni,
}
}
func newStorageNode(t *testing.T, c *neotest.ContractInvoker) testNodeInfo {
return dummyNodeInfo(c.NewAccount(t))
} }
func TestAddPeer(t *testing.T) { func TestAddPeer(t *testing.T) {
@ -74,25 +89,25 @@ func TestAddPeer(t *testing.T) {
acc1 := c.NewAccount(t) acc1 := c.NewAccount(t)
cAcc1 := c.WithSigners(acc1) cAcc1 := c.WithSigners(acc1)
cAcc1.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo) cAcc1.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo.raw)
h := cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo) h := cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo.raw)
aer := cAcc.CheckHalt(t, h) aer := cAcc.CheckHalt(t, h)
require.Equal(t, 1, len(aer.Events)) require.Equal(t, 1, len(aer.Events))
require.Equal(t, "AddPeer", aer.Events[0].Name) require.Equal(t, "AddPeer", aer.Events[0].Name)
require.Equal(t, stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(dummyInfo)}), require.Equal(t, stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(dummyInfo.raw)}),
aer.Events[0].Item) aer.Events[0].Item)
dummyInfo[0] ^= 0xFF dummyInfo.raw[0] ^= 0xFF
h = cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo) h = cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo.raw)
aer = cAcc.CheckHalt(t, h) aer = cAcc.CheckHalt(t, h)
require.Equal(t, 1, len(aer.Events)) require.Equal(t, 1, len(aer.Events))
require.Equal(t, "AddPeer", aer.Events[0].Name) require.Equal(t, "AddPeer", aer.Events[0].Name)
require.Equal(t, stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(dummyInfo)}), require.Equal(t, stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(dummyInfo.raw)}),
aer.Events[0].Item) aer.Events[0].Item)
c.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo) c.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo.raw)
c.Invoke(t, stackitem.Null{}, "register", dummyInfo) c.Invoke(t, stackitem.Null{}, "register", dummyInfo.raw)
} }
func TestUpdateState(t *testing.T) { func TestUpdateState(t *testing.T) {
@ -103,7 +118,7 @@ func TestUpdateState(t *testing.T) {
cBoth := e.WithSigners(e.Committee, acc) cBoth := e.WithSigners(e.Committee, acc)
dummyInfo := dummyNodeInfo(acc) dummyInfo := dummyNodeInfo(acc)
cBoth.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo) cBoth.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo.raw)
pub, ok := vm.ParseSignatureContract(acc.Script()) pub, ok := vm.ParseSignatureContract(acc.Script())
require.True(t, ok) require.True(t, ok)