forked from TrueCloudLab/frostfs-node
[#1008] ir/container: Customize fee for named container registration
In notary disabled environment, approval of container creation with nice name attribute takes much more additional GAS than other operations (due to NNS invocation). Morph library changes: * add the ability to specify per-op fees using `StaticClient` options; * add the ability to customize fee for `Put` operation with named container in container morph client. Inner Ring changes: * add `fee.named_container_register` config value which specifies additional GAS fee for the approvals of the named container registrations; * pass the config value to `WithCustomFeeForNamedPut` option of container morph client. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
b4c36a109d
commit
feb0a65efb
7 changed files with 185 additions and 34 deletions
|
@ -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", "")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
52
pkg/morph/client/fee.go
Normal file
52
pkg/morph/client/fee.go
Normal file
|
@ -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
|
||||
}
|
32
pkg/morph/client/fee_test.go
Normal file
32
pkg/morph/client/fee_test.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue