adm: Take network settings into account during netmap contract update #234

Merged
fyrchik merged 3 commits from acid-ant/frostfs-node:bugfix/100-adm-updt-contract into master 2023-04-14 05:12:51 +00:00
5 changed files with 173 additions and 105 deletions

View file

@ -6,6 +6,8 @@ Changelog for FrostFS Node
### Added
### Changed
### Fixed
- Take network settings into account during netmap contract update (#100)
### Removed
### Updated
### Updating from v0.36.0

View file

@ -10,12 +10,12 @@ import (
"strings"
"text/tabwriter"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@ -48,41 +48,28 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
buf := bytes.NewBuffer(nil)
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
for _, param := range arr {
tuple, ok := param.Value().([]stackitem.Item)
if !ok || len(tuple) != 2 {
return errors.New("invalid ListConfig response from netmap contract")
}
k, err := tuple[0].TryBytes()
m, err := parseConfigFromNetmapContract(arr)
if err != nil {
return errors.New("invalid config key from netmap contract")
return err
}
v, err := tuple[1].TryBytes()
if err != nil {
return invalidConfigValueErr(k)
}
switch string(k) {
case netmapAuditFeeKey, netmapBasicIncomeRateKey,
netmapContainerFeeKey, netmapContainerAliasFeeKey,
netmapEigenTrustIterationsKey,
netmapEpochKey, netmapInnerRingCandidateFeeKey,
netmapMaxObjectSizeKey, netmapWithdrawFeeKey:
for k, v := range m {
switch k {
case netmap.AuditFeeConfig, netmap.BasicIncomeRateConfig,
netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
netmap.EtIterationsConfig,
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig:
nbuf := make([]byte, 8)
copy(nbuf[:], v)
n := binary.LittleEndian.Uint64(nbuf)
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
case netmapEigenTrustAlphaKey:
case netmap.EtAlphaConfig:
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (str)\n", k, v)))
case netmapHomomorphicHashDisabledKey, netmapMaintenanceAllowedKey:
vBool, err := tuple[1].TryBool()
if err != nil {
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
if len(v) == 0 || len(v) > 1 {
return invalidConfigValueErr(k)
}
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, vBool)))
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, v[0] == 1)))
default:
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (hex)\n", k, hex.EncodeToString(v))))
}
@ -150,16 +137,16 @@ func parseConfigPair(kvStr string, force bool) (key string, val any, err error)
valRaw := v
switch key {
case netmapAuditFeeKey, netmapBasicIncomeRateKey,
netmapContainerFeeKey, netmapContainerAliasFeeKey,
netmapEigenTrustIterationsKey,
netmapEpochKey, netmapInnerRingCandidateFeeKey,
netmapMaxObjectSizeKey, netmapWithdrawFeeKey:
case netmap.AuditFeeConfig, netmap.BasicIncomeRateConfig,
netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
netmap.EtIterationsConfig,
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig:
val, err = strconv.ParseInt(valRaw, 10, 64)
if err != nil {
err = fmt.Errorf("could not parse %s's value '%s' as int: %w", key, valRaw, err)
}
case netmapEigenTrustAlphaKey:
case netmap.EtAlphaConfig:
// just check that it could
// be parsed correctly
_, err = strconv.ParseFloat(v, 64)
@ -168,7 +155,7 @@ func parseConfigPair(kvStr string, force bool) (key string, val any, err error)
}
val = valRaw
case netmapHomomorphicHashDisabledKey, netmapMaintenanceAllowedKey:
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
val, err = strconv.ParseBool(valRaw)
if err != nil {
err = fmt.Errorf("could not parse %s's value '%s' as bool: %w", key, valRaw, err)
@ -187,6 +174,6 @@ func parseConfigPair(kvStr string, force bool) (key string, val any, err error)
return
}
func invalidConfigValueErr(key []byte) error {
func invalidConfigValueErr(key string) error {
return fmt.Errorf("invalid %s config value from netmap contract", key)
}

View file

@ -16,6 +16,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
@ -23,6 +24,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
@ -30,8 +32,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/spf13/viper"
)
const (
@ -50,19 +52,6 @@ const (
)
const (
netmapEpochKey = "EpochDuration"
netmapMaxObjectSizeKey = "MaxObjectSize"
netmapAuditFeeKey = "AuditFee"
netmapContainerFeeKey = "ContainerFee"
netmapContainerAliasFeeKey = "ContainerAliasFee"
netmapEigenTrustIterationsKey = "EigenTrustIterations"
netmapEigenTrustAlphaKey = "EigenTrustAlpha"
netmapBasicIncomeRateKey = "BasicIncomeRate"
netmapInnerRingCandidateFeeKey = "InnerRingCandidateFee"
netmapWithdrawFeeKey = "WithdrawFee"
netmapHomomorphicHashDisabledKey = "HomomorphicHashingDisabled"
netmapMaintenanceAllowedKey = "MaintenanceModeAllowed"
defaultEigenTrustIterations = 4
defaultEigenTrustAlpha = "0.1"
)
@ -85,6 +74,21 @@ var (
nnsContract,
alphabetContract,
}, contractList...)
netmapConfigKeys = []string{
fyrchik marked this conversation as resolved Outdated

We use it only in 1 place, can we just iterate over a slice?

We use it only in 1 place, can we just iterate over a slice?

Done, please review.

Done, please review.
netmap.EpochDurationConfig,
netmap.MaxObjectSizeConfig,
netmap.AuditFeeConfig,
netmap.ContainerFeeConfig,
netmap.ContainerAliasFeeConfig,
netmap.EtIterationsConfig,
netmap.EtAlphaConfig,
netmap.BasicIncomeRateConfig,
netmap.IrCandidateFeeConfig,
netmap.WithdrawFeeConfig,
netmap.HomomorphicHashingDisabledKey,
netmap.MaintenanceModeAllowedConfig,
}
)
type contractState struct {
@ -239,7 +243,7 @@ func (c *initializeContext) deployOrUpdateContracts(w *io2.BufBinWriter, nnsHash
invokeHash = ctrHash
}
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam))
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam, updateMethodName))
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
if err != nil {
if method != updateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
@ -362,7 +366,7 @@ func (c *initializeContext) deployContracts() error {
return fmt.Errorf("can't sign manifest group: %v", err)
}
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam))
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam, deployMethodName))
res, err := c.CommitteeAct.MakeCall(management.Hash, deployMethodName, params...)
if err != nil {
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
@ -529,7 +533,7 @@ func getContractDeployParameters(cs *contractState, deployData []any) []any {
return []any{cs.RawNEF, cs.RawManifest, deployData}
}
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []any) []any {
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []any, method string) []any {
items := make([]any, 1, 6)
items[0] = false // notaryDisabled is false
@ -566,20 +570,31 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []an
c.Contracts[netmapContract].Hash,
c.Contracts[containerContract].Hash)
case netmapContract:
configParam := []any{
netmapEpochKey, viper.GetInt64(epochDurationInitFlag),
netmapMaxObjectSizeKey, viper.GetInt64(maxObjectSizeInitFlag),
netmapAuditFeeKey, viper.GetInt64(auditFeeInitFlag),
netmapContainerFeeKey, viper.GetInt64(containerFeeInitFlag),
netmapContainerAliasFeeKey, viper.GetInt64(containerAliasFeeInitFlag),
netmapEigenTrustIterationsKey, int64(defaultEigenTrustIterations),
netmapEigenTrustAlphaKey, defaultEigenTrustAlpha,
netmapBasicIncomeRateKey, viper.GetInt64(incomeRateInitFlag),
netmapInnerRingCandidateFeeKey, viper.GetInt64(candidateFeeInitFlag),
netmapWithdrawFeeKey, viper.GetInt64(withdrawFeeInitFlag),
netmapHomomorphicHashDisabledKey, viper.GetBool(homomorphicHashDisabledInitFlag),
netmapMaintenanceAllowedKey, viper.GetBool(maintenanceModeAllowedInitFlag),
md := getDefaultNetmapContractConfigMap()
if method == updateMethodName {
arr, err := c.getNetConfigFromNetmapContract()
if err != nil {
panic(err)

Why do we panic here?

Why do we panic here?

Used panic here because don't want to change method signature and this approach was used before in this method.

Used panic here because don't want to change method signature and this approach was used before in this method.
}
dstepanov-yadro marked this conversation as resolved Outdated

Do we really need panic? Maybe return error?

Do we really need panic? Maybe return error?

Used panic here because don't want to change method signature and this approach was used before in this method.

Used panic here because don't want to change method signature and this approach was used before in this method.
m, err := parseConfigFromNetmapContract(arr)
if err != nil {
panic(err)
}
for k, v := range m {
for _, key := range netmapConfigKeys {
if k == key {

Do we really need this dependency? >_<

Do we really need this dependency? >_<

I mean we could use it, but let's create a task to use it across the whole repo, I think there are other places which could benefit.

I mean we could use it, but let's create a task to use it across the whole repo, I think there are other places which could benefit.

Replaced slice with map.

Replaced slice with map.
md[k] = v
break
}
}
}
}
var configParam []any
for k, v := range md {
configParam = append(configParam, k, v)
}
items = append(items,
c.Contracts[balanceContract].Hash,
c.Contracts[containerContract].Hash,
@ -595,6 +610,22 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []an
return items
}
func (c *initializeContext) getNetConfigFromNetmapContract() ([]stackitem.Item, error) {
cs, err := c.Client.GetContractStateByID(1)
if err != nil {
return nil, fmt.Errorf("NNS is not yet deployed: %w", err)
}
nmHash, err := nnsResolveHash(c.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
if err != nil {
return nil, fmt.Errorf("can't get netmap contract hash: %w", err)
}
arr, err := unwrap.Array(c.ReadOnlyInvoker.Call(nmHash, "listConfig"))
if err != nil {
return nil, fmt.Errorf("can't fetch list of network config keys from the netmap contract")
}
return arr, err
}
func (c *initializeContext) getAlphabetDeployItems(i, n int) []any {
items := make([]any, 6)
items[0] = false

View file

@ -0,0 +1,48 @@
package morph
import (
"errors"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/spf13/viper"
)
func getDefaultNetmapContractConfigMap() map[string]any {
m := make(map[string]any)
m[netmap.EpochDurationConfig] = viper.GetInt64(epochDurationInitFlag)
m[netmap.MaxObjectSizeConfig] = viper.GetInt64(maxObjectSizeInitFlag)
m[netmap.AuditFeeConfig] = viper.GetInt64(auditFeeInitFlag)
m[netmap.ContainerFeeConfig] = viper.GetInt64(containerFeeInitFlag)
m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(containerAliasFeeInitFlag)
m[netmap.EtIterationsConfig] = int64(defaultEigenTrustIterations)
m[netmap.EtAlphaConfig] = defaultEigenTrustAlpha
m[netmap.BasicIncomeRateConfig] = viper.GetInt64(incomeRateInitFlag)
m[netmap.IrCandidateFeeConfig] = viper.GetInt64(candidateFeeInitFlag)
m[netmap.WithdrawFeeConfig] = viper.GetInt64(withdrawFeeInitFlag)
m[netmap.HomomorphicHashingDisabledKey] = viper.GetBool(homomorphicHashDisabledInitFlag)
m[netmap.MaintenanceModeAllowedConfig] = viper.GetBool(maintenanceModeAllowedInitFlag)
return m
}
func parseConfigFromNetmapContract(arr []stackitem.Item) (map[string][]byte, error) {
m := make(map[string][]byte, len(arr))
for _, param := range arr {
tuple, ok := param.Value().([]stackitem.Item)
if !ok || len(tuple) != 2 {
return nil, errors.New("invalid ListConfig response from netmap contract")
}
k, err := tuple[0].TryBytes()
if err != nil {
return nil, errors.New("invalid config key from netmap contract")
}
v, err := tuple[1].TryBytes()
if err != nil {
return nil, invalidConfigValueErr(string(k))
}
m[string(k)] = v
}
return m, nil
}

View file

@ -11,24 +11,24 @@ import (
)
const (
maxObjectSizeConfig = "MaxObjectSize"
basicIncomeRateConfig = "BasicIncomeRate"
auditFeeConfig = "AuditFee"
epochDurationConfig = "EpochDuration"
containerFeeConfig = "ContainerFee"
containerAliasFeeConfig = "ContainerAliasFee"
etIterationsConfig = "EigenTrustIterations"
etAlphaConfig = "EigenTrustAlpha"
irCandidateFeeConfig = "InnerRingCandidateFee"
withdrawFeeConfig = "WithdrawFee"
homomorphicHashingDisabledKey = "HomomorphicHashingDisabled"
maintenanceModeAllowedConfig = "MaintenanceModeAllowed"
MaxObjectSizeConfig = "MaxObjectSize"
BasicIncomeRateConfig = "BasicIncomeRate"
AuditFeeConfig = "AuditFee"
EpochDurationConfig = "EpochDuration"
ContainerFeeConfig = "ContainerFee"
ContainerAliasFeeConfig = "ContainerAliasFee"
EtIterationsConfig = "EigenTrustIterations"
EtAlphaConfig = "EigenTrustAlpha"
IrCandidateFeeConfig = "InnerRingCandidateFee"
WithdrawFeeConfig = "WithdrawFee"
HomomorphicHashingDisabledKey = "HomomorphicHashingDisabled"
MaintenanceModeAllowedConfig = "MaintenanceModeAllowed"
)
// MaxObjectSize receives max object size configuration
// value through the Netmap contract call.
func (c *Client) MaxObjectSize() (uint64, error) {
objectSize, err := c.readUInt64Config(maxObjectSizeConfig)
objectSize, err := c.readUInt64Config(MaxObjectSizeConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get epoch number: %w", c, err)
}
@ -39,7 +39,7 @@ func (c *Client) MaxObjectSize() (uint64, error) {
// BasicIncomeRate returns basic income rate configuration value from network
// config in netmap contract.
func (c *Client) BasicIncomeRate() (uint64, error) {
rate, err := c.readUInt64Config(basicIncomeRateConfig)
rate, err := c.readUInt64Config(BasicIncomeRateConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get basic income rate: %w", c, err)
}
@ -50,7 +50,7 @@ func (c *Client) BasicIncomeRate() (uint64, error) {
// AuditFee returns audit fee configuration value from network
// config in netmap contract.
func (c *Client) AuditFee() (uint64, error) {
fee, err := c.readUInt64Config(auditFeeConfig)
fee, err := c.readUInt64Config(AuditFeeConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get audit fee: %w", c, err)
}
@ -60,7 +60,7 @@ func (c *Client) AuditFee() (uint64, error) {
// EpochDuration returns number of sidechain blocks per one FrostFS epoch.
func (c *Client) EpochDuration() (uint64, error) {
epochDuration, err := c.readUInt64Config(epochDurationConfig)
epochDuration, err := c.readUInt64Config(EpochDurationConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get epoch duration: %w", c, err)
}
@ -71,7 +71,7 @@ func (c *Client) EpochDuration() (uint64, error) {
// ContainerFee returns fee paid by container owner to each alphabet node
// for container registration.
func (c *Client) ContainerFee() (uint64, error) {
fee, err := c.readUInt64Config(containerFeeConfig)
fee, err := c.readUInt64Config(ContainerFeeConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get container fee: %w", c, err)
}
@ -82,7 +82,7 @@ func (c *Client) ContainerFee() (uint64, error) {
// ContainerAliasFee returns additional fee paid by container owner to each
// alphabet node for container nice name registration.
func (c *Client) ContainerAliasFee() (uint64, error) {
fee, err := c.readUInt64Config(containerAliasFeeConfig)
fee, err := c.readUInt64Config(ContainerAliasFeeConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get container alias fee: %w", c, err)
}
@ -93,7 +93,7 @@ func (c *Client) ContainerAliasFee() (uint64, error) {
// EigenTrustIterations returns global configuration value of iteration cycles
// for EigenTrust algorithm per epoch.
func (c *Client) EigenTrustIterations() (uint64, error) {
iterations, err := c.readUInt64Config(etIterationsConfig)
iterations, err := c.readUInt64Config(EtIterationsConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get eigen trust iterations: %w", c, err)
}
@ -104,7 +104,7 @@ func (c *Client) EigenTrustIterations() (uint64, error) {
// EigenTrustAlpha returns global configuration value of alpha parameter.
// It receives the alpha as a string and tries to convert it to float.
func (c *Client) EigenTrustAlpha() (float64, error) {
strAlpha, err := c.readStringConfig(etAlphaConfig)
strAlpha, err := c.readStringConfig(EtAlphaConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get eigen trust alpha: %w", c, err)
}
@ -117,13 +117,13 @@ func (c *Client) EigenTrustAlpha() (float64, error) {
//
// Returns (false, nil) if config key is not found in the contract.
func (c *Client) HomomorphicHashDisabled() (bool, error) {
return c.readBoolConfig(homomorphicHashingDisabledKey)
return c.readBoolConfig(HomomorphicHashingDisabledKey)
}
// InnerRingCandidateFee returns global configuration value of fee paid by
// node to be in inner ring candidates list.
func (c *Client) InnerRingCandidateFee() (uint64, error) {
fee, err := c.readUInt64Config(irCandidateFeeConfig)
fee, err := c.readUInt64Config(IrCandidateFeeConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get inner ring candidate fee: %w", c, err)
}
@ -134,7 +134,7 @@ func (c *Client) InnerRingCandidateFee() (uint64, error) {
// WithdrawFee returns global configuration value of fee paid by user to
// withdraw assets from FrostFS contract.
func (c *Client) WithdrawFee() (uint64, error) {
fee, err := c.readUInt64Config(withdrawFeeConfig)
fee, err := c.readUInt64Config(WithdrawFeeConfig)
if err != nil {
return 0, fmt.Errorf("(%T) could not get withdraw fee: %w", c, err)
}
@ -148,7 +148,7 @@ func (c *Client) WithdrawFee() (uint64, error) {
//
// By default, maintenance state is disallowed.
func (c *Client) MaintenanceModeAllowed() (bool, error) {
return c.readBoolConfig(maintenanceModeAllowedConfig)
return c.readBoolConfig(MaintenanceModeAllowedConfig)
}
func (c *Client) readUInt64Config(key string) (uint64, error) {
@ -299,32 +299,32 @@ func (c *Client) ReadNetworkConfiguration() (NetworkConfiguration, error) {
Name: name,
Value: value,
})
case maxObjectSizeConfig:
case MaxObjectSizeConfig:
res.MaxObjectSize = bytesToUint64(value)
case basicIncomeRateConfig:
case BasicIncomeRateConfig:
res.StoragePrice = bytesToUint64(value)
case auditFeeConfig:
case AuditFeeConfig:
res.AuditFee = bytesToUint64(value)
case epochDurationConfig:
case EpochDurationConfig:
res.EpochDuration = bytesToUint64(value)
case containerFeeConfig:
case ContainerFeeConfig:
res.ContainerFee = bytesToUint64(value)
case containerAliasFeeConfig:
case ContainerAliasFeeConfig:
res.ContainerAliasFee = bytesToUint64(value)
case etIterationsConfig:
case EtIterationsConfig:
res.EigenTrustIterations = bytesToUint64(value)
case etAlphaConfig:
case EtAlphaConfig:
res.EigenTrustAlpha, err = strconv.ParseFloat(string(value), 64)
if err != nil {
return fmt.Errorf("invalid prm %s: %v", etAlphaConfig, err)
return fmt.Errorf("invalid prm %s: %v", EtAlphaConfig, err)
}
case irCandidateFeeConfig:
case IrCandidateFeeConfig:
res.IRCandidateFee = bytesToUint64(value)
case withdrawFeeConfig:
case WithdrawFeeConfig:
res.WithdrawalFee = bytesToUint64(value)
case homomorphicHashingDisabledKey:
case HomomorphicHashingDisabledKey:
res.HomomorphicHashingDisabled = bytesToBool(value)
case maintenanceModeAllowedConfig:
case MaintenanceModeAllowedConfig:
res.MaintenanceModeAllowed = bytesToBool(value)
}