package netmap import ( "context" "errors" "fmt" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) const ( MaxObjectSizeConfig = "MaxObjectSize" MaxECParityCountConfig = "MaxECParityCount" MaxECDataCountConfig = "MaxECDataCount" EpochDurationConfig = "EpochDuration" ContainerFeeConfig = "ContainerFee" ContainerAliasFeeConfig = "ContainerAliasFee" 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) if err != nil { return 0, fmt.Errorf("(%T) could not get epoch number: %w", c, err) } return objectSize, nil } // EpochDuration returns number of sidechain blocks per one FrostFS epoch. func (c *Client) EpochDuration() (uint64, error) { epochDuration, err := c.readUInt64Config(EpochDurationConfig) if err != nil { return 0, fmt.Errorf("(%T) could not get epoch duration: %w", c, err) } return epochDuration, nil } // 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) if err != nil { return 0, fmt.Errorf("(%T) could not get container fee: %w", c, err) } return fee, nil } // 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) if err != nil { return 0, fmt.Errorf("(%T) could not get container alias fee: %w", c, err) } return fee, nil } // HomomorphicHashDisabled returns global configuration value of homomorphic hashing // settings. // // Returns (false, nil) if config key is not found in the contract. func (c *Client) HomomorphicHashDisabled() (bool, error) { 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) if err != nil { return 0, fmt.Errorf("(%T) could not get inner ring candidate fee: %w", c, err) } return fee, nil } // 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) if err != nil { return 0, fmt.Errorf("(%T) could not get withdraw fee: %w", c, err) } return fee, nil } // MaintenanceModeAllowed reads admission of "maintenance" state from the // FrostFS network configuration stored in the Sidechain. The admission means // that storage nodes are allowed to switch their state to "maintenance". // // By default, maintenance state is disallowed. func (c *Client) MaintenanceModeAllowed() (bool, error) { return c.readBoolConfig(MaintenanceModeAllowedConfig) } func (c *Client) readUInt64Config(key string) (uint64, error) { v, err := c.config([]byte(key), IntegerAssert) if err != nil { return 0, err } // IntegerAssert is guaranteed to return int64 if the error is nil. return uint64(v.(int64)), nil } // reads boolean value by the given key from the FrostFS network configuration // stored in the Sidechain. Returns false if key is not presented. func (c *Client) readBoolConfig(key string) (bool, error) { v, err := c.config([]byte(key), BoolAssert) if err != nil { if errors.Is(err, ErrConfigNotFound) { return false, nil } return false, fmt.Errorf("read boolean configuration value %s from the Sidechain: %w", key, err) } // BoolAssert is guaranteed to return bool if the error is nil. return v.(bool), nil } // SetConfigPrm groups parameters of SetConfig operation. type SetConfigPrm struct { id []byte key []byte value any client.InvokePrmOptional } // SetID sets ID of the config value. func (s *SetConfigPrm) SetID(id []byte) { s.id = id } // SetKey sets key of the config value. func (s *SetConfigPrm) SetKey(key []byte) { s.key = key } // SetValue sets value of the config value. func (s *SetConfigPrm) SetValue(value any) { s.value = value } // SetConfig sets config field. func (c *Client) SetConfig(ctx context.Context, p SetConfigPrm) error { prm := client.InvokePrm{} prm.SetMethod(setConfigMethod) prm.SetArgs(p.id, p.key, p.value) prm.InvokePrmOptional = p.InvokePrmOptional _, err := c.client.Invoke(ctx, prm) return err } // RawNetworkParameter is a FrostFS network parameter which is transmitted but // not interpreted by the FrostFS API protocol. type RawNetworkParameter struct { // Name of the parameter. Name string // Raw parameter value. Value []byte } // NetworkConfiguration represents FrostFS network configuration stored // in the FrostFS Sidechain. type NetworkConfiguration struct { MaxObjectSize uint64 EpochDuration uint64 ContainerFee uint64 ContainerAliasFee uint64 IRCandidateFee uint64 WithdrawalFee uint64 HomomorphicHashingDisabled bool MaintenanceModeAllowed bool Raw []RawNetworkParameter } // ReadNetworkConfiguration reads NetworkConfiguration from the FrostFS Sidechain. func (c *Client) ReadNetworkConfiguration() (NetworkConfiguration, error) { var res NetworkConfiguration prm := client.TestInvokePrm{} prm.SetMethod(configListMethod) items, err := c.client.TestInvoke(prm) if err != nil { return res, fmt.Errorf("could not perform test invocation (%s): %w", configListMethod, err) } if ln := len(items); ln != 1 { return res, fmt.Errorf("unexpected stack item count (%s): %d", configListMethod, ln) } arr, err := client.ArrayFromStackItem(items[0]) if err != nil { return res, fmt.Errorf("record list (%s): %w", configListMethod, err) } m := make(map[string]struct{}, len(arr)) res.Raw = make([]RawNetworkParameter, 0, len(arr)) err = iterateRecords(arr, func(name string, value []byte) error { _, ok := m[name] if ok { return fmt.Errorf("duplicated config name %s", name) } m[name] = struct{}{} switch name { default: res.Raw = append(res.Raw, RawNetworkParameter{ Name: name, Value: value, }) case MaxObjectSizeConfig: res.MaxObjectSize = bytesToUint64(value) case EpochDurationConfig: res.EpochDuration = bytesToUint64(value) case ContainerFeeConfig: res.ContainerFee = bytesToUint64(value) case ContainerAliasFeeConfig: res.ContainerAliasFee = bytesToUint64(value) case IrCandidateFeeConfig: res.IRCandidateFee = bytesToUint64(value) case WithdrawFeeConfig: res.WithdrawalFee = bytesToUint64(value) case HomomorphicHashingDisabledKey: res.HomomorphicHashingDisabled = bytesToBool(value) case MaintenanceModeAllowedConfig: res.MaintenanceModeAllowed = bytesToBool(value) } return nil }) return res, err } func bytesToUint64(val []byte) uint64 { if len(val) == 0 { return 0 } return bigint.FromBytes(val).Uint64() } func bytesToBool(val []byte) bool { for i := range val { if val[i] != 0 { return true } } return false } // ErrConfigNotFound is returned when the requested key was not found // in the network config (returned value is `Null`). var ErrConfigNotFound = errors.New("config value not found") // config performs the test invoke of get config value // method of FrostFS Netmap contract. // // Returns ErrConfigNotFound if config key is not found in the contract. func (c *Client) config(key []byte, assert func(stackitem.Item) (any, error)) (any, error) { prm := client.TestInvokePrm{} prm.SetMethod(configMethod) prm.SetArgs(key) items, err := c.client.TestInvoke(prm) if err != nil { return nil, fmt.Errorf("could not perform test invocation (%s): %w", configMethod, err) } if ln := len(items); ln != 1 { return nil, fmt.Errorf("unexpected stack item count (%s): %d", configMethod, ln) } if _, ok := items[0].(stackitem.Null); ok { return nil, ErrConfigNotFound } return assert(items[0]) } // IntegerAssert converts stack item to int64. func IntegerAssert(item stackitem.Item) (any, error) { return client.IntFromStackItem(item) } // StringAssert converts stack item to string. func StringAssert(item stackitem.Item) (any, error) { return client.StringFromStackItem(item) } // BoolAssert converts stack item to bool. func BoolAssert(item stackitem.Item) (any, error) { return client.BoolFromStackItem(item) } // iterateRecords iterates over all config records and passes them to f. // // Returns f's errors directly. func iterateRecords(arr []stackitem.Item, f func(key string, value []byte) error) error { for i := range arr { fields, err := client.ArrayFromStackItem(arr[i]) if err != nil { return fmt.Errorf("record fields: %w", err) } if ln := len(fields); ln != 2 { return fmt.Errorf("unexpected record fields number: %d", ln) } k, err := client.BytesFromStackItem(fields[0]) if err != nil { return fmt.Errorf("record key: %w", err) } v, err := client.BytesFromStackItem(fields[1]) if err != nil { return fmt.Errorf("record value: %w", err) } if err := f(string(k), v); err != nil { return err } } return nil }