413 lines
13 KiB
Go
413 lines
13 KiB
Go
package tests
|
|
|
|
import (
|
|
"math/big"
|
|
"math/rand"
|
|
"path"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/container"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/netmap"
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const netmapPath = "../netmap"
|
|
|
|
func deployNetmapContract(t *testing.T, e *neotest.Executor, addrBalance, addrContainer util.Uint160, config ...any) util.Uint160 {
|
|
_, pubs, ok := vm.ParseMultiSigContract(e.Committee.Script())
|
|
require.True(t, ok)
|
|
|
|
args := make([]any, 4)
|
|
args[0] = addrBalance
|
|
args[1] = addrContainer
|
|
args[2] = []any{pubs[0]}
|
|
args[3] = append([]any{}, config...)
|
|
|
|
c := neotest.CompileFile(t, e.CommitteeHash, netmapPath, path.Join(netmapPath, "config.yml"))
|
|
e.DeployContract(t, c, args)
|
|
return c.Hash
|
|
}
|
|
|
|
func newNetmapInvoker(t *testing.T, config ...any) *neotest.ContractInvoker {
|
|
e := newExecutor(t)
|
|
|
|
ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
|
|
ctrNetmap := neotest.CompileFile(t, e.CommitteeHash, netmapPath, path.Join(netmapPath, "config.yml"))
|
|
ctrBalance := neotest.CompileFile(t, e.CommitteeHash, balancePath, path.Join(balancePath, "config.yml"))
|
|
ctrContainer := neotest.CompileFile(t, e.CommitteeHash, containerPath, path.Join(containerPath, "config.yml"))
|
|
|
|
e.DeployContract(t, ctrNNS, nil)
|
|
deployContainerContract(t, e, ctrNetmap.Hash, ctrBalance.Hash, ctrNNS.Hash)
|
|
deployBalanceContract(t, e, ctrNetmap.Hash, ctrContainer.Hash)
|
|
deployNetmapContract(t, e, ctrBalance.Hash, ctrContainer.Hash, config...)
|
|
return e.CommitteeInvoker(ctrNetmap.Hash)
|
|
}
|
|
|
|
func TestDeploySetConfig(t *testing.T) {
|
|
c := newNetmapInvoker(t, "SomeKey", "TheValue", container.AliasFeeKey, int64(123))
|
|
c.Invoke(t, "TheValue", "config", "SomeKey")
|
|
c.Invoke(t, stackitem.NewByteArray(bigint.ToBytes(big.NewInt(123))),
|
|
"config", container.AliasFeeKey)
|
|
}
|
|
|
|
type testNodeInfo struct {
|
|
signer neotest.SingleSigner
|
|
pub []byte
|
|
raw []byte
|
|
state netmap.NodeState
|
|
}
|
|
|
|
func dummyNodeInfo(acc neotest.Signer) testNodeInfo {
|
|
ni := make([]byte, 66)
|
|
rand.Read(ni)
|
|
|
|
s := acc.(neotest.SingleSigner)
|
|
pub := s.Account().PrivateKey().PublicKey().Bytes()
|
|
copy(ni[2:], pub)
|
|
return testNodeInfo{
|
|
signer: s,
|
|
pub: pub,
|
|
raw: ni,
|
|
state: netmap.NodeStateOnline,
|
|
}
|
|
}
|
|
|
|
func newStorageNode(t *testing.T, c *neotest.ContractInvoker) testNodeInfo {
|
|
return dummyNodeInfo(c.NewAccount(t))
|
|
}
|
|
|
|
func TestAddPeer(t *testing.T) {
|
|
c := newNetmapInvoker(t)
|
|
|
|
acc := c.NewAccount(t)
|
|
cAcc := c.WithSigners(acc)
|
|
dummyInfo := dummyNodeInfo(acc)
|
|
|
|
acc1 := c.NewAccount(t)
|
|
cAcc1 := c.WithSigners(acc1)
|
|
cAcc1.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo.raw)
|
|
|
|
h := cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo.raw)
|
|
aer := cAcc.CheckHalt(t, h)
|
|
require.Equal(t, 0, len(aer.Events))
|
|
|
|
dummyInfo.raw[0] ^= 0xFF
|
|
h = cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo.raw)
|
|
aer = cAcc.CheckHalt(t, h)
|
|
require.Equal(t, 0, len(aer.Events))
|
|
|
|
c.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo.raw)
|
|
c.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo.raw)
|
|
}
|
|
|
|
func TestNewEpoch(t *testing.T) {
|
|
rand.Seed(42)
|
|
|
|
const epochCount = netmap.DefaultSnapshotCount * 2
|
|
|
|
cNm := newNetmapInvoker(t)
|
|
nodes := make([][]testNodeInfo, epochCount)
|
|
for i := range nodes {
|
|
size := rand.Int()%5 + 1
|
|
arr := make([]testNodeInfo, size)
|
|
for j := 0; j < size; j++ {
|
|
arr[j] = newStorageNode(t, cNm)
|
|
}
|
|
nodes[i] = arr
|
|
}
|
|
|
|
for i := 0; i < epochCount; i++ {
|
|
for _, tn := range nodes[i] {
|
|
cNm.WithSigners(tn.signer).Invoke(t, stackitem.Null{}, "addPeer", tn.raw)
|
|
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", tn.raw)
|
|
}
|
|
|
|
if i > 0 {
|
|
// Remove random nodes from the previous netmap.
|
|
current := make([]testNodeInfo, 0, len(nodes[i])+len(nodes[i-1]))
|
|
current = append(current, nodes[i]...)
|
|
|
|
for j := range nodes[i-1] {
|
|
if rand.Int()%3 == 0 {
|
|
cNm.Invoke(t, stackitem.Null{}, "updateStateIR",
|
|
int64(netmap.NodeStateOffline), nodes[i-1][j].pub)
|
|
} else {
|
|
current = append(current, nodes[i-1][j])
|
|
}
|
|
}
|
|
nodes[i] = current
|
|
}
|
|
cNm.Invoke(t, stackitem.Null{}, "newEpoch", i+1)
|
|
|
|
t.Logf("Epoch: %d, Netmap()", i)
|
|
s, err := cNm.TestInvoke(t, "netmap")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, s.Len())
|
|
checkSnapshot(t, s, nodes[i])
|
|
|
|
for j := 0; j <= i && j < netmap.DefaultSnapshotCount; j++ {
|
|
t.Logf("Epoch: %d, diff: %d", i, j)
|
|
checkSnapshotAt(t, j, cNm, nodes[i-j])
|
|
}
|
|
|
|
_, err = cNm.TestInvoke(t, "snapshot", netmap.DefaultSnapshotCount)
|
|
require.Error(t, err)
|
|
require.True(t, strings.Contains(err.Error(), "incorrect diff"))
|
|
|
|
_, err = cNm.TestInvoke(t, "snapshot", -1)
|
|
require.Error(t, err)
|
|
require.True(t, strings.Contains(err.Error(), "incorrect diff"))
|
|
}
|
|
}
|
|
|
|
func TestUpdateSnapshotCount(t *testing.T) {
|
|
rand.Seed(42)
|
|
|
|
require.True(t, netmap.DefaultSnapshotCount > 5) // sanity check, adjust tests if false.
|
|
|
|
prepare := func(t *testing.T, cNm *neotest.ContractInvoker, epochCount int) [][]testNodeInfo {
|
|
nodes := make([][]testNodeInfo, epochCount)
|
|
nodes[0] = []testNodeInfo{newStorageNode(t, cNm)}
|
|
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", nodes[0][0].raw)
|
|
cNm.Invoke(t, stackitem.Null{}, "newEpoch", 1)
|
|
for i := 1; i < len(nodes); i++ {
|
|
sn := newStorageNode(t, cNm)
|
|
nodes[i] = append(nodes[i-1], sn)
|
|
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", sn.raw)
|
|
cNm.Invoke(t, stackitem.Null{}, "newEpoch", i+1)
|
|
}
|
|
return nodes
|
|
}
|
|
|
|
t.Run("increase size, extend with nil", func(t *testing.T) {
|
|
// Before: S-old .. S
|
|
// After : S-old .. S nil nil ...
|
|
const epochCount = netmap.DefaultSnapshotCount / 2
|
|
|
|
cNm := newNetmapInvoker(t)
|
|
nodes := prepare(t, cNm, epochCount)
|
|
|
|
const newCount = netmap.DefaultSnapshotCount + 3
|
|
cNm.Invoke(t, stackitem.Null{}, "updateSnapshotCount", newCount)
|
|
|
|
s, err := cNm.TestInvoke(t, "netmap")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, s.Len())
|
|
checkSnapshot(t, s, nodes[epochCount-1])
|
|
for i := 0; i < epochCount; i++ {
|
|
checkSnapshotAt(t, i, cNm, nodes[epochCount-i-1])
|
|
}
|
|
for i := epochCount; i < newCount; i++ {
|
|
checkSnapshotAt(t, i, cNm, nil)
|
|
}
|
|
_, err = cNm.TestInvoke(t, "snapshot", int64(newCount))
|
|
require.Error(t, err)
|
|
})
|
|
t.Run("increase size, copy old snapshots", func(t *testing.T) {
|
|
// Before: S-x .. S S-old ...
|
|
// After : S-x .. S nil nil S-old ...
|
|
const epochCount = netmap.DefaultSnapshotCount + netmap.DefaultSnapshotCount/2
|
|
|
|
cNm := newNetmapInvoker(t)
|
|
nodes := prepare(t, cNm, epochCount)
|
|
|
|
const newCount = netmap.DefaultSnapshotCount + 3
|
|
cNm.Invoke(t, stackitem.Null{}, "updateSnapshotCount", newCount)
|
|
|
|
s, err := cNm.TestInvoke(t, "netmap")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, s.Len())
|
|
checkSnapshot(t, s, nodes[epochCount-1])
|
|
for i := 0; i < newCount-3; i++ {
|
|
checkSnapshotAt(t, i, cNm, nodes[epochCount-i-1])
|
|
}
|
|
for i := newCount - 3; i < newCount; i++ {
|
|
checkSnapshotAt(t, i, cNm, nil)
|
|
}
|
|
_, err = cNm.TestInvoke(t, "snapshot", int64(newCount))
|
|
require.Error(t, err)
|
|
})
|
|
t.Run("decrease size, small decrease", func(t *testing.T) {
|
|
// Before: S-x .. S S-old ... ...
|
|
// After : S-x .. S S-new ...
|
|
const epochCount = netmap.DefaultSnapshotCount + netmap.DefaultSnapshotCount/2
|
|
|
|
cNm := newNetmapInvoker(t)
|
|
nodes := prepare(t, cNm, epochCount)
|
|
|
|
const newCount = netmap.DefaultSnapshotCount/2 + 2
|
|
cNm.Invoke(t, stackitem.Null{}, "updateSnapshotCount", newCount)
|
|
|
|
s, err := cNm.TestInvoke(t, "netmap")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, s.Len())
|
|
checkSnapshot(t, s, nodes[epochCount-1])
|
|
for i := 0; i < newCount; i++ {
|
|
checkSnapshotAt(t, i, cNm, nodes[epochCount-i-1])
|
|
}
|
|
_, err = cNm.TestInvoke(t, "snapshot", int64(newCount))
|
|
require.Error(t, err)
|
|
})
|
|
t.Run("decrease size, big decrease", func(t *testing.T) {
|
|
// Before: S-x ... ... S S-old ... ...
|
|
// After : S-new ... S
|
|
const epochCount = netmap.DefaultSnapshotCount + netmap.DefaultSnapshotCount/2
|
|
|
|
cNm := newNetmapInvoker(t)
|
|
nodes := prepare(t, cNm, epochCount)
|
|
|
|
const newCount = netmap.DefaultSnapshotCount/2 - 2
|
|
cNm.Invoke(t, stackitem.Null{}, "updateSnapshotCount", newCount)
|
|
|
|
s, err := cNm.TestInvoke(t, "netmap")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, s.Len())
|
|
checkSnapshot(t, s, nodes[epochCount-1])
|
|
for i := 0; i < newCount; i++ {
|
|
checkSnapshotAt(t, i, cNm, nodes[epochCount-i-1])
|
|
}
|
|
_, err = cNm.TestInvoke(t, "snapshot", int64(newCount))
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func checkSnapshotAt(t *testing.T, epoch int, cNm *neotest.ContractInvoker, nodes []testNodeInfo) {
|
|
s, err := cNm.TestInvoke(t, "snapshot", int64(epoch))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, s.Len())
|
|
checkSnapshot(t, s, nodes)
|
|
}
|
|
|
|
func checkSnapshot(t *testing.T, s *vm.Stack, nodes []testNodeInfo) {
|
|
arr, ok := s.Pop().Value().([]stackitem.Item)
|
|
require.True(t, ok, "expected array")
|
|
require.Equal(t, len(nodes), len(arr), "expected %d nodes", len(nodes))
|
|
|
|
actual := make([]netmap.Node, len(nodes))
|
|
expected := make([]netmap.Node, len(nodes))
|
|
for i := range nodes {
|
|
n, ok := arr[i].Value().([]stackitem.Item)
|
|
require.True(t, ok, "expected node struct")
|
|
require.Equalf(t, 2, len(n), "expected %d field(s)", 2)
|
|
|
|
require.IsType(t, []byte{}, n[0].Value())
|
|
|
|
state, err := n[1].TryInteger()
|
|
require.NoError(t, err)
|
|
|
|
actual[i].BLOB = n[0].Value().([]byte)
|
|
actual[i].State = netmap.NodeState(state.Int64())
|
|
expected[i].BLOB = nodes[i].raw
|
|
expected[i].State = nodes[i].state
|
|
}
|
|
|
|
require.ElementsMatch(t, expected, actual, "snapshot is different")
|
|
}
|
|
|
|
func TestUpdateStateIR(t *testing.T) {
|
|
cNm := newNetmapInvoker(t)
|
|
|
|
acc := cNm.NewAccount(t)
|
|
pub := acc.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()
|
|
|
|
t.Run("can't move online, need addPeerIR", func(t *testing.T) {
|
|
cNm.InvokeFail(t, "peer is missing", "updateStateIR", int64(netmap.NodeStateOnline), pub)
|
|
})
|
|
|
|
dummyInfo := dummyNodeInfo(acc)
|
|
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo.raw)
|
|
|
|
acc1 := cNm.NewAccount(t)
|
|
dummyInfo1 := dummyNodeInfo(acc1)
|
|
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo1.raw)
|
|
|
|
t.Run("must be signed by the alphabet", func(t *testing.T) {
|
|
cAcc := cNm.WithSigners(acc)
|
|
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "updateStateIR", int64(netmap.NodeStateOffline), pub)
|
|
})
|
|
t.Run("invalid state", func(t *testing.T) {
|
|
cNm.InvokeFail(t, "unsupported state", "updateStateIR", int64(42), pub)
|
|
})
|
|
|
|
checkNetmapCandidates(t, cNm, 2)
|
|
|
|
// Move the first node offline.
|
|
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateOffline), pub)
|
|
checkNetmapCandidates(t, cNm, 1)
|
|
|
|
checkState := func(expected netmap.NodeState) {
|
|
arr := checkNetmapCandidates(t, cNm, 1)
|
|
nn := arr[0].Value().([]stackitem.Item)
|
|
state, err := nn[1].TryInteger()
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(expected), state.Int64())
|
|
}
|
|
|
|
// Move the second node in the maintenance state.
|
|
pub1 := acc1.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()
|
|
t.Run("maintenance -> add peer", func(t *testing.T) {
|
|
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateMaintenance), pub1)
|
|
checkState(netmap.NodeStateMaintenance)
|
|
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo1.raw)
|
|
checkState(netmap.NodeStateOnline)
|
|
})
|
|
t.Run("maintenance -> online", func(t *testing.T) {
|
|
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateMaintenance), pub1)
|
|
checkState(netmap.NodeStateMaintenance)
|
|
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateOnline), pub1)
|
|
checkState(netmap.NodeStateOnline)
|
|
})
|
|
}
|
|
|
|
func TestUpdateState(t *testing.T) {
|
|
cNm := newNetmapInvoker(t)
|
|
|
|
accs := []neotest.Signer{cNm.NewAccount(t), cNm.NewAccount(t)}
|
|
pubs := make([][]byte, len(accs))
|
|
for i := range accs {
|
|
dummyInfo := dummyNodeInfo(accs[i])
|
|
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo.raw)
|
|
pubs[i] = accs[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()
|
|
}
|
|
|
|
t.Run("missing witness", func(t *testing.T) {
|
|
cAcc := cNm.WithSigners(accs[0])
|
|
cNm.InvokeFail(t, common.ErrWitnessFailed, "updateState", int64(2), pubs[0])
|
|
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "updateState", int64(2), pubs[0])
|
|
cAcc.InvokeFail(t, common.ErrWitnessFailed, "updateState", int64(2), pubs[1])
|
|
})
|
|
|
|
checkNetmapCandidates(t, cNm, 2)
|
|
|
|
cBoth := cNm.WithSigners(accs[0], cNm.Committee)
|
|
|
|
cBoth.Invoke(t, stackitem.Null{}, "updateState", int64(2), pubs[0])
|
|
checkNetmapCandidates(t, cNm, 1)
|
|
|
|
t.Run("remove already removed node", func(t *testing.T) {
|
|
cBoth.Invoke(t, stackitem.Null{}, "updateState", int64(2), pubs[0])
|
|
checkNetmapCandidates(t, cNm, 1)
|
|
})
|
|
|
|
cBoth = cNm.WithSigners(accs[1], cNm.Committee)
|
|
cBoth.Invoke(t, stackitem.Null{}, "updateState", int64(2), pubs[1])
|
|
checkNetmapCandidates(t, cNm, 0)
|
|
}
|
|
|
|
func checkNetmapCandidates(t *testing.T, c *neotest.ContractInvoker, size int) []stackitem.Item {
|
|
s, err := c.TestInvoke(t, "netmapCandidates")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, s.Len())
|
|
|
|
arr, ok := s.Pop().Value().([]stackitem.Item)
|
|
require.True(t, ok)
|
|
require.Equal(t, size, len(arr))
|
|
return arr
|
|
}
|