package morph import ( "encoding/hex" "fmt" "os" "path/filepath" "strconv" "testing" "time" cmdConfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/node" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/policy" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/spf13/viper" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" ) const ( contractsPath = "../../../../../../contract/frostfs-contract-v0.18.0.tar.gz" protoFileName = "proto.yml" ) func TestInitialize(t *testing.T) { // This test needs frostfs-contract tarball, so it is skipped by default. // It is here for performing local testing after the changes. t.Skip() t.Run("1 nodes", func(t *testing.T) { testInitialize(t, 1) }) t.Run("4 nodes", func(t *testing.T) { testInitialize(t, 4) }) t.Run("7 nodes", func(t *testing.T) { testInitialize(t, 7) }) t.Run("16 nodes", func(t *testing.T) { testInitialize(t, 16) }) t.Run("max nodes", func(t *testing.T) { testInitialize(t, util.MaxAlphabetNodes) }) t.Run("too many nodes", func(t *testing.T) { require.ErrorIs(t, generateTestData(t, t.TempDir(), util.MaxAlphabetNodes+1), util.ErrTooManyAlphabetNodes) }) } func testInitialize(t *testing.T, committeeSize int) { testdataDir := t.TempDir() v := viper.GetViper() require.NoError(t, generateTestData(t, testdataDir, committeeSize)) v.Set(util.ProtoConfigPath, filepath.Join(testdataDir, protoFileName)) // Set to the path or remove the next statement to download from the network. require.NoError(t, initCmd.Flags().Set(util.ContractsInitFlag, contractsPath)) dumpPath := filepath.Join(testdataDir, "out") require.NoError(t, initCmd.Flags().Set(util.LocalDumpFlag, dumpPath)) v.Set(util.AlphabetWalletsFlag, testdataDir) v.Set(util.EpochDurationInitFlag, 1) v.Set(util.MaxObjectSizeInitFlag, 1024) setTestCredentials(v, committeeSize) require.NoError(t, initializeSideChainCmd(initCmd, nil)) t.Run("force-new-epoch", func(t *testing.T) { require.NoError(t, netmap.ForceNewEpoch.Flags().Set(util.LocalDumpFlag, dumpPath)) require.NoError(t, netmap.ForceNewEpochCmd(netmap.ForceNewEpoch, nil)) }) t.Run("set-config", func(t *testing.T) { require.NoError(t, cmdConfig.SetCmd.Flags().Set(util.LocalDumpFlag, dumpPath)) require.NoError(t, cmdConfig.SetConfigCmd(cmdConfig.SetCmd, []string{"MaintenanceModeAllowed=true"})) }) t.Run("set-policy", func(t *testing.T) { require.NoError(t, policy.Set.Flags().Set(util.LocalDumpFlag, dumpPath)) require.NoError(t, policy.SetPolicyCmd(policy.Set, []string{"ExecFeeFactor=1"})) }) t.Run("remove-node", func(t *testing.T) { pk, err := keys.NewPrivateKey() require.NoError(t, err) pub := hex.EncodeToString(pk.PublicKey().Bytes()) require.NoError(t, node.RemoveCmd.Flags().Set(util.LocalDumpFlag, dumpPath)) require.NoError(t, node.RemoveNodesCmd(node.RemoveCmd, []string{pub})) }) } func generateTestData(t *testing.T, dir string, size int) error { v := viper.GetViper() v.Set(util.AlphabetWalletsFlag, dir) sizeStr := strconv.FormatUint(uint64(size), 10) if err := generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr); err != nil { return err } setTestCredentials(v, size) if err := generateAlphabetCreds(generateAlphabetCmd, nil); err != nil { return err } var pubs []string for i := 0; i < size; i++ { p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json") w, err := wallet.NewWalletFromFile(p) if err != nil { return fmt.Errorf("wallet doesn't exist: %w", err) } for _, acc := range w.Accounts { if acc.Label == util.SingleAccountName { pub, ok := vm.ParseSignatureContract(acc.Contract.Script) if !ok { return fmt.Errorf("could not parse signature script for %s", acc.Address) } pubs = append(pubs, hex.EncodeToString(pub)) continue } } } cfg := config.Config{} cfg.ProtocolConfiguration.Magic = 12345 cfg.ProtocolConfiguration.ValidatorsCount = uint32(size) cfg.ProtocolConfiguration.TimePerBlock = time.Second cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters cfg.ProtocolConfiguration.P2PSigExtensions = true cfg.ProtocolConfiguration.VerifyTransactions = true data, err := yaml.Marshal(cfg) if err != nil { return err } protoPath := filepath.Join(dir, protoFileName) return os.WriteFile(protoPath, data, os.ModePerm) } func setTestCredentials(v *viper.Viper, size int) { for i := 0; i < size; i++ { v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10)) } v.Set("credentials.contract", testContractPassword) } func TestNextPollInterval(t *testing.T) { var pollInterval time.Duration var iteration int pollInterval, hasChanged := util.NextPollInterval(iteration, pollInterval) require.True(t, hasChanged) require.Equal(t, time.Second, pollInterval) iteration = 4 pollInterval, hasChanged = util.NextPollInterval(iteration, pollInterval) require.False(t, hasChanged) require.Equal(t, time.Second, pollInterval) iteration = 5 pollInterval, hasChanged = util.NextPollInterval(iteration, pollInterval) require.True(t, hasChanged) require.Equal(t, 2*time.Second, pollInterval) iteration = 10 pollInterval, hasChanged = util.NextPollInterval(iteration, pollInterval) require.True(t, hasChanged) require.Equal(t, 4*time.Second, pollInterval) iteration = 20 pollInterval = 32 * time.Second pollInterval, hasChanged = util.NextPollInterval(iteration, pollInterval) require.True(t, hasChanged) // from 32s to 16s require.Equal(t, 16*time.Second, pollInterval) pollInterval = 16 * time.Second pollInterval, hasChanged = util.NextPollInterval(iteration, pollInterval) require.False(t, hasChanged) require.Equal(t, 16*time.Second, pollInterval) }