diff --git a/cmd/neofs-ir/defaults.go b/cmd/neofs-ir/defaults.go index 1a466ab4..09c8724b 100644 --- a/cmd/neofs-ir/defaults.go +++ b/cmd/neofs-ir/defaults.go @@ -109,8 +109,9 @@ func defaultConfiguration(cfg *viper.Viper) { cfg.SetDefault("locode.db.path", "") // extra fee values for working mode without notary contract - cfg.SetDefault("fee.main_chain", 5000_0000) // 0.5 Fixed8 - cfg.SetDefault("fee.side_chain", 2_0000_0000) // 2.0 Fixed8 + cfg.SetDefault("fee.main_chain", 5000_0000) // 0.5 Fixed8 + cfg.SetDefault("fee.side_chain", 2_0000_0000) // 2.0 Fixed8 + cfg.SetDefault("fee.named_container_register", 25_0000_0000) // 25.0 Fixed8 cfg.SetDefault("control.authorized_keys", []string{}) cfg.SetDefault("control.grpc.endpoint", "") diff --git a/pkg/innerring/config/fee.go b/pkg/innerring/config/fee.go index f448f1ba..54eeb6ab 100644 --- a/pkg/innerring/config/fee.go +++ b/pkg/innerring/config/fee.go @@ -8,13 +8,19 @@ import ( // FeeConfig is an instance that returns extra fee values for contract // invocations without notary support. type FeeConfig struct { - mainchain, sidechain fixedn.Fixed8 + registerNamedCnr, + mainchain, + sidechain fixedn.Fixed8 } +// NewFeeConfig constructs FeeConfig from viper.Viper instance. Latter must not be nil. +// +// Fee for named container registration is taken from "fee.named_container_register" value. func NewFeeConfig(v *viper.Viper) *FeeConfig { return &FeeConfig{ - mainchain: fixedn.Fixed8(v.GetInt64("fee.main_chain")), - sidechain: fixedn.Fixed8(v.GetInt64("fee.side_chain")), + registerNamedCnr: fixedn.Fixed8(v.GetInt64("fee.named_container_register")), + mainchain: fixedn.Fixed8(v.GetInt64("fee.main_chain")), + sidechain: fixedn.Fixed8(v.GetInt64("fee.side_chain")), } } @@ -25,3 +31,8 @@ func (f FeeConfig) MainChainFee() fixedn.Fixed8 { func (f FeeConfig) SideChainFee() fixedn.Fixed8 { return f.sidechain } + +// NamedContainerRegistrationFee returns additional GAS fee for named container registration in NeoFS network. +func (f FeeConfig) NamedContainerRegistrationFee() fixedn.Fixed8 { + return f.registerNamedCnr +} diff --git a/pkg/innerring/innerring.go b/pkg/innerring/innerring.go index 2a1241f1..350a3f3c 100644 --- a/pkg/innerring/innerring.go +++ b/pkg/innerring/innerring.go @@ -466,7 +466,22 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper) (*Server, error return nil, err } - cnrClient, err := cntWrapper.NewFromMorph(server.morphClient, server.contracts.container, fee, cntWrapper.TryNotary(), cntWrapper.AsAlphabet()) + // form morph container client's options + morphCnrOpts := make([]cntWrapper.Option, 0, 3) + morphCnrOpts = append(morphCnrOpts, + cntWrapper.TryNotary(), + cntWrapper.AsAlphabet(), + ) + + if server.sideNotaryConfig.disabled { + // in non-notary environments we customize fee for named container registration + // because it takes much more additional GAS than other operations. + morphCnrOpts = append(morphCnrOpts, + cntWrapper.WithCustomFeeForNamedPut(server.feeConfig.NamedContainerRegistrationFee()), + ) + } + + cnrClient, err := cntWrapper.NewFromMorph(server.morphClient, server.contracts.container, fee, morphCnrOpts...) if err != nil { return nil, err } diff --git a/pkg/morph/client/container/wrapper/wrapper.go b/pkg/morph/client/container/wrapper/wrapper.go index de762fa8..11c844a6 100644 --- a/pkg/morph/client/container/wrapper/wrapper.go +++ b/pkg/morph/client/container/wrapper/wrapper.go @@ -28,7 +28,12 @@ type Wrapper struct { // parameter of Wrapper. type Option func(*opts) -type opts []client.StaticClientOption +type opts struct { + feePutNamedSet bool + feePutNamed fixedn.Fixed8 + + staticOpts []client.StaticClientOption +} func defaultOpts() *opts { return new(opts) @@ -43,7 +48,7 @@ func (w Wrapper) Morph() *client.Client { // notary invocation tries. func TryNotary() Option { return func(o *opts) { - *o = append(*o, client.TryNotary()) + o.staticOpts = append(o.staticOpts, client.TryNotary()) } } @@ -54,11 +59,22 @@ func TryNotary() Option { // Considered to be used by IR nodes only. func AsAlphabet() Option { return func(o *opts) { - *o = append(*o, client.AsAlphabet()) + o.staticOpts = append(o.staticOpts, client.AsAlphabet()) + } +} + +// WithCustomFeeForNamedPut returns option to specify custom fee for each Put operation with named container. +func WithCustomFeeForNamedPut(fee fixedn.Fixed8) Option { + return func(o *opts) { + o.feePutNamed = fee + o.feePutNamedSet = true } } // NewFromMorph returns the wrapper instance from the raw morph client. +// +// Specified fee is used for all operations by default. If WithCustomFeeForNamedPut is provided, +// the customized fee is used for Put operations with named containers. func NewFromMorph(cli *client.Client, contract util.Uint160, fee fixedn.Fixed8, opts ...Option) (*Wrapper, error) { o := defaultOpts() @@ -66,12 +82,27 @@ func NewFromMorph(cli *client.Client, contract util.Uint160, fee fixedn.Fixed8, opts[i](o) } - staticClient, err := client.NewStatic(cli, contract, fee, ([]client.StaticClientOption)(*o)...) + // below is working but not the best solution to customize fee for PutNamed operation + // It is done like that because container package doesn't provide option to specify the fee. + // In the future, we will possibly get rid of the container package at all. + const methodNamePutNamed = "putNamed" + + var ( + staticOpts = o.staticOpts + cnrOpts = make([]container.Option, 0, 1) + ) + + if o.feePutNamedSet { + staticOpts = append(staticOpts, client.WithCustomFee(methodNamePutNamed, o.feePutNamed)) + cnrOpts = append(cnrOpts, container.WithPutNamedMethod(methodNamePutNamed)) + } + + staticClient, err := client.NewStatic(cli, contract, fee, staticOpts...) if err != nil { return nil, fmt.Errorf("can't create container static client: %w", err) } - enhancedContainerClient, err := container.New(staticClient) + enhancedContainerClient, err := container.New(staticClient, cnrOpts...) if err != nil { return nil, fmt.Errorf("can't create container morph client: %w", err) } diff --git a/pkg/morph/client/fee.go b/pkg/morph/client/fee.go new file mode 100644 index 00000000..98016b9b --- /dev/null +++ b/pkg/morph/client/fee.go @@ -0,0 +1,52 @@ +package client + +import "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + +// customFees represents source of customized per-operation fees. +// Can be initialized using var declaration. +// +// Instances are not thread-safe, so they mean initially filling, and then only reading. +type customFees map[string]fixedn.Fixed8 + +// setFeeForMethod sets fee for the operation executed using specified contract method. +func (x *customFees) setFeeForMethod(method string, fee fixedn.Fixed8) { + m := *x + if m == nil { + m = make(map[string]fixedn.Fixed8, 1) + *x = m + } + + m[method] = fee +} + +// returns customized for the operation executed using specified contract method. +// Returns false if fee is not customized. +func (x customFees) feeForMethod(method string) (fixedn.Fixed8, bool) { + v, ok := x[method] + return v, ok +} + +// fees represents source of per-operation fees. +// Can be initialized using var declaration. +// +// Instances are not thread-safe, so they mean initially filling, and then only reading. +type fees struct { + defaultFee fixedn.Fixed8 + + customFees +} + +// sets default fee for all operations. +func (x *fees) setDefault(fee fixedn.Fixed8) { + x.defaultFee = fee +} + +// returns fee for the operation executed using specified contract method. +// Returns customized value if it is set. Otherwise, returns default value. +func (x fees) feeForMethod(method string) fixedn.Fixed8 { + if fee, ok := x.customFees.feeForMethod(method); ok { + return fee + } + + return x.defaultFee +} diff --git a/pkg/morph/client/fee_test.go b/pkg/morph/client/fee_test.go new file mode 100644 index 00000000..f33dba48 --- /dev/null +++ b/pkg/morph/client/fee_test.go @@ -0,0 +1,32 @@ +package client + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/stretchr/testify/require" +) + +func TestFees(t *testing.T) { + var v fees + + const method = "some method" + + var ( + fee fixedn.Fixed8 + def = fixedn.Fixed8(13) + ) + + v.setDefault(def) + + fee = v.feeForMethod(method) + require.True(t, fee.Equal(def)) + + const customFee = fixedn.Fixed8(10) + + v.setFeeForMethod(method, customFee) + + fee = v.feeForMethod(method) + + require.Equal(t, customFee, fee) +} diff --git a/pkg/morph/client/static.go b/pkg/morph/client/static.go index efa3d9f3..3e13794b 100644 --- a/pkg/morph/client/static.go +++ b/pkg/morph/client/static.go @@ -17,29 +17,24 @@ import ( // expression (or just declaring a StaticClient variable) is unsafe // and can lead to panic. type StaticClient struct { - tryNotary bool - alpha bool // use client's key to sign notary request's main TX + staticOpts client *Client // neo-go client instance scScriptHash util.Uint160 // contract script-hash - - fee fixedn.Fixed8 // invocation fee } type staticOpts struct { tryNotary bool - alpha bool + alpha bool // use client's key to sign notary request's main TX + + fees fees } // StaticClientOption allows to set an optional // parameter of StaticClient. type StaticClientOption func(*staticOpts) -func defaultStaticOpts() *staticOpts { - return new(staticOpts) -} - // ErrNilStaticClient is returned by functions that expect // a non-nil StaticClient pointer, but received nil. var ErrNilStaticClient = errors.New("static client is nil") @@ -47,24 +42,25 @@ var ErrNilStaticClient = errors.New("static client is nil") // NewStatic creates, initializes and returns the StaticClient instance. // // If provided Client instance is nil, ErrNilClient is returned. +// +// Specified fee is used by default. Per-operation fees can be customized via WithCustomFee option. func NewStatic(client *Client, scriptHash util.Uint160, fee fixedn.Fixed8, opts ...StaticClientOption) (*StaticClient, error) { if client == nil { return nil, ErrNilClient } - o := defaultStaticOpts() - - for i := range opts { - opts[i](o) - } - - return &StaticClient{ - tryNotary: o.tryNotary, - alpha: o.alpha, + c := &StaticClient{ client: client, scScriptHash: scriptHash, - fee: fee, - }, nil + } + + c.fees.setDefault(fee) + + for i := range opts { + opts[i](&c.staticOpts) + } + + return c, nil } // Morph return wrapped raw morph client. @@ -117,7 +113,12 @@ func (i *InvokePrmOptional) SetHash(hash util.Uint256) { // If TryNotary is provided: // - if AsAlphabet is provided, calls NotaryInvoke; // - otherwise, calls NotaryInvokeNotAlpha. +// +// If fee for the operation executed using specified method is customized, then StaticClient uses it. +// Otherwise, default fee is used. func (s StaticClient) Invoke(prm InvokePrm) error { + fee := s.fees.feeForMethod(prm.method) + if s.tryNotary { if s.alpha { var ( @@ -136,15 +137,15 @@ func (s StaticClient) Invoke(prm InvokePrm) error { vubP = &vub } - return s.client.NotaryInvoke(s.scScriptHash, s.fee, nonce, vubP, prm.method, prm.args...) + return s.client.NotaryInvoke(s.scScriptHash, fee, nonce, vubP, prm.method, prm.args...) } - return s.client.NotaryInvokeNotAlpha(s.scScriptHash, s.fee, prm.method, prm.args...) + return s.client.NotaryInvokeNotAlpha(s.scScriptHash, fee, prm.method, prm.args...) } return s.client.Invoke( s.scScriptHash, - s.fee, + fee, prm.method, prm.args..., ) @@ -198,3 +199,11 @@ func AsAlphabet() StaticClientOption { o.alpha = true } } + +// WithCustomFee returns option to specify custom fee for the operation executed using +// specified contract method. +func WithCustomFee(method string, fee fixedn.Fixed8) StaticClientOption { + return func(o *staticOpts) { + o.fees.setFeeForMethod(method, fee) + } +}