diff --git a/cmd/frostfs-adm/docs/deploy.md b/cmd/frostfs-adm/docs/deploy.md index aead65fe0d..974c2a93c7 100644 --- a/cmd/frostfs-adm/docs/deploy.md +++ b/cmd/frostfs-adm/docs/deploy.md @@ -18,6 +18,7 @@ To start a network, you need a set of consensus nodes, the same number of Alphabet nodes and any number of Storage nodes. While the number of Storage nodes can be scaled almost infinitely, the number of consensus and Alphabet nodes can't be changed so easily right now. Consider this before going any further. +Note also that there is an upper limit on the number of alphabet nodes (currently 22). It is easier to use`frostfs-adm` with a predefined configuration. First, create a network configuration file. In this example, there is going to be only one diff --git a/cmd/frostfs-adm/internal/modules/morph/generate.go b/cmd/frostfs-adm/internal/modules/morph/generate.go index dd327a4b12..ccdc4519f2 100644 --- a/cmd/frostfs-adm/internal/modules/morph/generate.go +++ b/cmd/frostfs-adm/internal/modules/morph/generate.go @@ -39,6 +39,9 @@ func generateAlphabetCreds(cmd *cobra.Command, _ []string) error { if size == 0 { return errors.New("size must be > 0") } + if size > maxAlphabetNodes { + return ErrTooManyAlphabetNodes + } v := viper.GetViper() walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag)) diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize.go b/cmd/frostfs-adm/internal/modules/morph/initialize.go index 494ad52964..9eb867faae 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize.go @@ -23,6 +23,13 @@ import ( "github.com/spf13/viper" ) +const ( + // maxAlphabetNodes is the maximum number of candidates allowed, which is currently limited by the size + // of the invocation script. + // See: https://github.com/nspcc-dev/neo-go/blob/740488f7f35e367eaa99a71c0a609c315fe2b0fc/pkg/core/transaction/witness.go#L10 + maxAlphabetNodes = 22 +) + type cache struct { nnsCs *state.Contract groupKey *keys.PublicKey @@ -45,6 +52,8 @@ type initializeContext struct { ContractPath string } +var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", maxAlphabetNodes) + func initializeSideChainCmd(cmd *cobra.Command, _ []string) error { initCtx, err := newInitializeContext(cmd, viper.GetViper()) if err != nil { @@ -111,6 +120,10 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex return nil, err } + if len(wallets) > maxAlphabetNodes { + return nil, ErrTooManyAlphabetNodes + } + needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init" var w *wallet.Wallet diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_test.go b/cmd/frostfs-adm/internal/modules/morph/initialize_test.go index e2e5aa0ada..07d2da8cc5 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_test.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_test.go @@ -2,6 +2,7 @@ package morph import ( "encoding/hex" + "fmt" "os" "path/filepath" "strconv" @@ -40,8 +41,11 @@ func TestInitialize(t *testing.T) { t.Run("16 nodes", func(t *testing.T) { testInitialize(t, 16) }) - t.Run("22 nodes", func(t *testing.T) { - testInitialize(t, 22) + t.Run("max nodes", func(t *testing.T) { + testInitialize(t, maxAlphabetNodes) + }) + t.Run("too many nodes", func(t *testing.T) { + require.ErrorIs(t, generateTestData(t, t.TempDir(), maxAlphabetNodes+1), ErrTooManyAlphabetNodes) }) } @@ -49,7 +53,7 @@ func testInitialize(t *testing.T, committeeSize int) { testdataDir := t.TempDir() v := viper.GetViper() - generateTestData(t, testdataDir, committeeSize) + require.NoError(t, generateTestData(t, testdataDir, committeeSize)) v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName)) // Set to the path or remove the next statement to download from the network. @@ -80,25 +84,33 @@ func testInitialize(t *testing.T, committeeSize int) { }) } -func generateTestData(t *testing.T, dir string, size int) { +func generateTestData(t *testing.T, dir string, size int) error { v := viper.GetViper() v.Set(alphabetWalletsFlag, dir) sizeStr := strconv.FormatUint(uint64(size), 10) - require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr)) + if err := generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr); err != nil { + return err + } setTestCredentials(v, size) - require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil)) + 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) - require.NoError(t, err, "wallet doesn't exist") + if err != nil { + return fmt.Errorf("wallet doesn't exist: %w", err) + } for _, acc := range w.Accounts { if acc.Label == singleAccountName { pub, ok := vm.ParseSignatureContract(acc.Contract.Script) - require.True(t, ok) + if !ok { + return fmt.Errorf("could not parse signature script for %s", acc.Address) + } pubs = append(pubs, hex.EncodeToString(pub)) continue } @@ -113,10 +125,12 @@ func generateTestData(t *testing.T, dir string, size int) { cfg.ProtocolConfiguration.P2PSigExtensions = true cfg.ProtocolConfiguration.VerifyTransactions = true data, err := yaml.Marshal(cfg) - require.NoError(t, err) + if err != nil { + return err + } protoPath := filepath.Join(dir, protoFileName) - require.NoError(t, os.WriteFile(protoPath, data, os.ModePerm)) + return os.WriteFile(protoPath, data, os.ModePerm) } func setTestCredentials(v *viper.Viper, size int) {