frostfs-node/pkg/morph/client/constructor.go
Leonard Lyubich ad7ad12a0c [#746] morph: Implement and use multi-client
There is a need to work with a set of Neo RPC nodes in order not to depend
on the failure of some nodes while others are active.

Support "multi-client" mode of morph `Client` entity. If instance is not
"multi-client", it works as before. Constructor `New` creates multi-client,
and each method performs iterating over the fixed set of endpoints until
success. Opened client connections are cached (without eviction for now).

Storage (as earlier) and IR (from now) nodes can be configured with multiple
Neo endpoints. As above, `New` creates multi-client instance, so we don't
need initialization changes on app-side.

`Wait` and `GetDesignateHash` methods of `Client` return an error from now
to detect connection errors. `NotaryEnabled` method is removed as unused.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
2021-09-02 11:04:38 +03:00

154 lines
3.6 KiB
Go

package client
import (
"context"
"time"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
"go.uber.org/zap"
)
// Option is a client configuration change function.
type Option func(*cfg)
// groups the configurations with default values.
type cfg struct {
ctx context.Context // neo-go client context
dialTimeout time.Duration // client dial timeout
logger *logger.Logger // logging component
gas util.Uint160 // native gas script-hash
waitInterval time.Duration
signer *transaction.Signer
extraEndpoints []string
}
const (
defaultDialTimeout = 5 * time.Second
defaultWaitInterval = 500 * time.Millisecond
)
func defaultConfig() *cfg {
return &cfg{
ctx: context.Background(),
dialTimeout: defaultDialTimeout,
logger: zap.L(),
waitInterval: defaultWaitInterval,
signer: &transaction.Signer{
Scopes: transaction.Global,
},
}
}
// New creates, initializes and returns the Client instance.
// Notary support should be enabled with EnableNotarySupport client
// method separately.
//
// If private key is nil, it panics.
//
// Other values are set according to provided options, or by default:
// * client context: Background;
// * dial timeout: 5s;
// * blockchain network type: netmode.PrivNet;
// * logger: zap.L().
//
// If desired option satisfies the default value, it can be omitted.
// If multiple options of the same config value are supplied,
// the option with the highest index in the arguments will be used.
func New(key *keys.PrivateKey, endpoint string, opts ...Option) (*Client, error) {
if key == nil {
panic("empty private key")
}
// build default configuration
cfg := defaultConfig()
// apply options
for _, opt := range opts {
opt(cfg)
}
endpoints := append(cfg.extraEndpoints, endpoint)
return &Client{
multiClient: &multiClient{
cfg: *cfg,
account: wallet.NewAccountFromPrivateKey(key),
endpoints: endpoints,
clients: make(map[string]*Client, len(endpoints)),
},
}, nil
}
// WithContext returns a client constructor option that
// specifies the neo-go client context.
//
// Ignores nil value.
//
// If option not provided, context.Background() is used.
func WithContext(ctx context.Context) Option {
return func(c *cfg) {
if ctx != nil {
c.ctx = ctx
}
}
}
// WithDialTimeout returns a client constructor option
// that specifies neo-go client dial timeout duration.
//
// Ignores non-positive value.
//
// If option not provided, 5s timeout is used.
func WithDialTimeout(dur time.Duration) Option {
return func(c *cfg) {
if dur > 0 {
c.dialTimeout = dur
}
}
}
// WithLogger returns a client constructor option
// that specifies the component for writing log messages.
//
// Ignores nil value.
//
// If option not provided, zap.L() is used.
func WithLogger(logger *logger.Logger) Option {
return func(c *cfg) {
if logger != nil {
c.logger = logger
}
}
}
// WithSigner returns a client constructor option
// that specifies the signer and the scope of the transaction.
//
// Ignores nil value.
//
// If option not provided, signer with global scope is used.
func WithSigner(signer *transaction.Signer) Option {
return func(c *cfg) {
if signer != nil {
c.signer = signer
}
}
}
// WithExtraEndpoints returns a client constructor option
// that specifies additional Neo rpc endpoints.
func WithExtraEndpoints(endpoints []string) Option {
return func(c *cfg) {
c.extraEndpoints = append(c.extraEndpoints, endpoints...)
}
}