diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d4ce0b3eb..01364a3220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Changelog for NeoFS Node - Support of verified domains for the storage nodes (#2280) - `neofs-lens storage status` CLI command (#2550) - Human-readable output of objects' creation timestamp to `neofs-cli container list-objects` (#2653) +- Ability to preset P2PNotary and NeoFSAlphabet roles to validators at the FS chain's genesis (#2643) ### Fixed - `neofs-cli netmap netinfo` documentation (#2555) diff --git a/config/example/ir.yaml b/config/example/ir.yaml index 8944094a95..337e461c43 100644 --- a/config/example/ir.yaml +++ b/config/example/ir.yaml @@ -21,8 +21,11 @@ morph: - 0283120f4c8c1fc1d792af5063d2def9da5fddc90bc1384de7fcfdda33c3860170 consensus: # Local consensus launch mode activated only when 'endpoint.client' is unset. magic: 15405 # Network magic. Must be unsigned integer in range [1:4294967295] - committee: # Initial committee - - 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2 # Hex-encoded public key + committee: # Hex-encoded public keys of the initial committee + - 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2 + - 02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e + - 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699 + - 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62 storage: # Blockchain storage type: boltdb # One of following storage types: # boltdb (local BoltDB) @@ -41,8 +44,13 @@ morph: - node3:20333 hardforks: # Optional hard-forks name: 1730000 # Maps name to chain height. Heights must not be greater than 4294967295 - validators_history: # Optional number of consensus nodes to use after given height - 10: 7 # Maps chain height to number of consensus nodes. Values must not be greater than 2147483647 + validators_history: # Optional number of consensus nodes to use after given height. + # Maps chain height to number of consensus nodes. Heights must be multiples of the 'committee' size. + # Values must be positive up to 'committee' size. + # If specified, value for 0 (genesis) height must be set. + 0: 4 + 4: 1 + 12: 4 rpc: # Optional RPC settings listen: # Optional list of network addresses to listen Neo RPC on. By default, protocol is not served # TCP addresses in 'host:port' format @@ -75,6 +83,9 @@ morph: ping: # Optional settings of pinging mechanism interval: 30s # Optional time period between pings. Defaults to 30s. Must not be negative timeout: 90s # Optional time period to wait for pong. Defaults to 1m. Must not be negative + set_roles_in_genesis: true # Optional flag for designating P2PNotary and NeoFSAlphabet roles to all + # genesis block validators. The validators depend on 'committee' and, if set, 'validators_history'. + # Must be 'true' or 'false'. fschain_autodeploy: true # Optional flag to run auto-deployment procedure of the FS chain. By default, # the chain is expected to be deployed/updated in the background (e.g. via NeoFS ADM tool). diff --git a/pkg/innerring/config.go b/pkg/innerring/config.go index 6feb03f5d4..d6427a32cb 100644 --- a/pkg/innerring/config.go +++ b/pkg/innerring/config.go @@ -111,16 +111,24 @@ func parseBlockchainConfig(v *viper.Viper, _logger *zap.Logger) (c blockchain.Co const validatorsHistoryKey = rootSection + ".validators_history" if v.IsSet(validatorsHistoryKey) { c.ValidatorsHistory = make(map[uint32]uint32) + committeeSize := uint64(c.Committee.Len()) err = parseConfigMap(v, validatorsHistoryKey, "validators history", func(name string, val any) error { height, err := strconv.ParseUint(name, 10, 32) if err != nil { return fmt.Errorf("parse unsigned integer: %w", err) } + + if height%committeeSize != 0 { + return fmt.Errorf("height %d is not a multiple of the %q size", height, committeeKey) + } + num, err := cast.ToUint32E(val) if err != nil { return err - } else if num > math.MaxInt32 { + } else if num <= 0 { return fmt.Errorf("value %d is out of allowable range", num) + } else if num > uint32(c.Committee.Len()) { + return fmt.Errorf("value exceeds %q size: %d > %d", committeeKey, num, c.Committee.Len()) } c.ValidatorsHistory[uint32(height)] = num return nil @@ -210,6 +218,11 @@ func parseBlockchainConfig(v *viper.Viper, _logger *zap.Logger) (c blockchain.Co } } + c.SetRolesInGenesis, err = parseConfigBool(v, rootSection+".set_roles_in_genesis", "flag to set roles for the committee in the genesis block") + if err != nil && !errors.Is(err, errMissingConfig) { + return c, err + } + c.Logger = _logger return c, nil diff --git a/pkg/innerring/config_test.go b/pkg/innerring/config_test.go index 5d6492b45b..1638058ec0 100644 --- a/pkg/innerring/config_test.go +++ b/pkg/innerring/config_test.go @@ -23,6 +23,8 @@ morph: committee: - 02cddc58c3f7d27b5c9967dd90fbd4269798cbbb9cd7b137d886aca209cb734fb6 - 03f87b0a0416e4028bccf7258db3b411412ce1c7426b2c857f54e59d0d23782570 + - 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699 + - 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62 storage: type: boltdb path: chain.db @@ -38,8 +40,9 @@ const validBlockchainConfigOptions = ` hardforks: name: 1730000 validators_history: - 2: 3 - 10: 7 + 0: 4 + 4: 1 + 12: 4 rpc: listen: - localhost:30000 @@ -64,6 +67,7 @@ const validBlockchainConfigOptions = ` ping: interval: 44s timeout: 55s + set_roles_in_genesis: true ` func _newConfigFromYAML(tb testing.TB, yaml1, yaml2 string) *viper.Viper { @@ -124,6 +128,8 @@ func TestParseBlockchainConfig(t *testing.T) { validCommittee, err := keys.NewPublicKeysFromStrings([]string{ "02cddc58c3f7d27b5c9967dd90fbd4269798cbbb9cd7b137d886aca209cb734fb6", "03f87b0a0416e4028bccf7258db3b411412ce1c7426b2c857f54e59d0d23782570", + "03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699", + "02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62", }) require.NoError(t, err) @@ -190,9 +196,11 @@ func TestParseBlockchainConfig(t *testing.T) { }, Storage: blockchain.BoltDB("chain.db"), ValidatorsHistory: map[uint32]uint32{ - 2: 3, - 10: 7, + 0: 4, + 4: 1, + 12: 4, }, + SetRolesInGenesis: true, }, c) }) @@ -246,10 +254,12 @@ func TestParseBlockchainConfig(t *testing.T) { {kvF("hardforks", map[string]any{"name": -1})}, {kvF("hardforks", map[string]any{"name": math.MaxUint32 + 1})}, {kvF("validators_history", map[string]any{"not a number": 1})}, - {kvF("validators_history", map[string]any{"1": "not a number"})}, + {kvF("validators_history", map[string]any{"0": "not a number"})}, {kvF("validators_history", map[string]any{"-1": 1})}, - {kvF("validators_history", map[string]any{"1": -1})}, - {kvF("validators_history", map[string]any{"1": math.MaxInt32 + 1})}, + {kvF("validators_history", map[string]any{"0": -1})}, + {kvF("validators_history", map[string]any{"0": math.MaxUint32 + 1})}, + {kvF("validators_history", map[string]any{"0": len(validCommittee) + 1})}, + {kvF("validators_history", map[string]any{"0": 1, "3": 1})}, // height is not a multiple {kvF("rpc.listen", []string{"not a TCP address"})}, {kvF("rpc.listen", []string{"127.0.0.1"})}, // missing port {kvF("rpc.tls.enabled", true), kvF("rpc.tls.cert_file", "")}, // enabled but no cert file is provided @@ -272,6 +282,11 @@ func TestParseBlockchainConfig(t *testing.T) { {kvF("p2p.peers.max", math.MaxInt32+1)}, {kvF("p2p.peers.attempts", -1)}, {kvF("p2p.peers.attempts", math.MaxInt32+1)}, + {kvF("set_roles_in_genesis", "not a boolean")}, + {kvF("set_roles_in_genesis", 1)}, + {kvF("set_roles_in_genesis", "True")}, + {kvF("set_roles_in_genesis", "False")}, + {kvF("set_roles_in_genesis", "not a boolean")}, } { var reportMsg []string diff --git a/pkg/innerring/internal/blockchain/blockchain.go b/pkg/innerring/internal/blockchain/blockchain.go index 85aff39ec9..1165b8a6fe 100644 --- a/pkg/innerring/internal/blockchain/blockchain.go +++ b/pkg/innerring/internal/blockchain/blockchain.go @@ -12,6 +12,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/consensus" "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -235,9 +236,17 @@ type Config struct { // Maps chain height to number of consensus nodes. // - // Optional: by default Committee size is used. Each value must not be greater - // than math.MaxInt32. + // Optional: by default Committee size is used. Each value must be positive and + // must not exceed Committee length. Value for zero key (genesis height) is + // required. ValidatorsHistory map[uint32]uint32 + + // Whether to designate [noderoles.P2PNotary] and [noderoles.NeoFSAlphabet] + // roles to the Committee (keep an eye on ValidatorsHistory) for genesis block + // in the RoleManagement contract. + // + // Optional: by default, roles are unset. + SetRolesInGenesis bool } // New returns new Blockchain configured by the specified Config. New panics if @@ -274,9 +283,24 @@ func New(cfg Config) (res *Blockchain, err error) { panic("negative proto tick interval") } - for height, num := range cfg.ValidatorsHistory { - if num > math.MaxInt32 { - panic(fmt.Sprintf("number of validators at height %d is out of allowable range %d", height, num)) + if cfg.ValidatorsHistory != nil { + _, ok := cfg.ValidatorsHistory[0] + if !ok { + panic("missing 0 (genesis) height in validators history") + } + + committeeSize := uint32(cfg.Committee.Len()) + + for height, num := range cfg.ValidatorsHistory { + if height%committeeSize != 0 { + panic(fmt.Sprintf("validators history's height is not a multiple of the committee size: %d%%%d != 0", height, committeeSize)) + } + if num == 0 { + panic(fmt.Sprintf("zero number of validators at height %d", height)) + } + if num > committeeSize { + panic(fmt.Sprintf("number of validators at height %d exceeds committee: %d > %d", height, num, committeeSize)) + } } } @@ -334,6 +358,16 @@ func New(cfg Config) (res *Blockchain, err error) { cfgBaseProto.ValidatorsCount = uint32(len(standByCommittee)) } + if cfg.SetRolesInGenesis { + // note that ValidatorsHistory or ValidatorsCount field must be set above + genesisValidatorsCount := cfgBaseProto.GetNumOfCNs(0) + cfgBaseProto.Genesis.Roles = map[noderoles.Role]keys.PublicKeys{ + // Notary service is always enabled, see below + noderoles.P2PNotary: cfg.Committee[:genesisValidatorsCount], + noderoles.NeoFSAlphabet: cfg.Committee[:genesisValidatorsCount], + } + } + cfgBaseApp := &cfgBase.ApplicationConfiguration cfgBaseApp.Relay = true cfgBaseApp.Consensus.Enabled = true