package neofs import ( "context" "errors" "strings" "time" "git.marketally.com/tutus-one/tutus-chain/pkg/config" "git.marketally.com/tutus-one/tutus-chain/pkg/wallet" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/pool" "github.com/nspcc-dev/neofs-sdk-go/user" ) // Constants related to NeoFS block storage. const ( // DefaultTimeout is the default timeout for NeoFS requests. DefaultTimeout = 10 * time.Minute // DefaultDownloaderWorkersCount is the default number of workers downloading blocks. DefaultDownloaderWorkersCount = 500 // DefaultBatchSize is the default size of the batch to upload in parallel // and search latest fully uploaded batch. DefaultBatchSize = 128000 // DefaultBlockAttribute is the default attribute name for block objects. DefaultBlockAttribute = "Block" // DefaultStateAttribute is the default attribute name for state objects. DefaultStateAttribute = "State" // DefaultKVBatchSize is a number of contract storage key-value objects to // flush to the node's DB in a batch. DefaultKVBatchSize = 1000 // DefaultSearchBatchSize is a number of objects to search in a batch. DefaultSearchBatchSize = 1000 ) // Constants related to NeoFS pool request timeouts. const ( // DefaultDialTimeout is a default timeout used to establish connection with // NeoFS storage nodes. DefaultDialTimeout = 30 * time.Second // DefaultStreamTimeout is a default timeout used for NeoFS streams processing. // It has significantly large value to reliably avoid timeout problems with heavy // SEARCH requests. DefaultStreamTimeout = 10 * time.Minute // DefaultHealthcheckTimeout is a timeout for request to NeoFS storage node to // decide if it is alive. DefaultHealthcheckTimeout = 10 * time.Second ) // Constants related to retry mechanism. const ( // MaxRetries is the maximum number of retries for a single operation. MaxRetries = 5 // InitialBackoff is the initial backoff duration. InitialBackoff = 500 * time.Millisecond // BackoffFactor is the factor by which the backoff duration is multiplied. BackoffFactor = 2 // MaxBackoff is the maximum backoff duration. MaxBackoff = 20 * time.Second ) // BasicService is a minimal service structure for NeoFS fetchers. type BasicService struct { Pool *pool.Pool Account *wallet.Account ContainerID cid.ID Ctx context.Context CtxCancel context.CancelFunc } // NewBasicService creates a new BasicService instance. func NewBasicService(cfg config.NeoFSService) (BasicService, error) { var ( account *wallet.Account containerID cid.ID err error ) if cfg.UnlockWallet.Path != "" { walletFromFile, err := wallet.NewWalletFromFile(cfg.UnlockWallet.Path) if err != nil { return BasicService{}, err } for _, acc := range walletFromFile.Accounts { if err = acc.Decrypt(cfg.UnlockWallet.Password, walletFromFile.Scrypt); err == nil { account = acc break } } if account == nil { return BasicService{}, errors.New("failed to decrypt any account in the wallet") } } else { account, err = wallet.NewAccount() if err != nil { return BasicService{}, err } } params := pool.DefaultOptions() params.SetHealthcheckTimeout(DefaultHealthcheckTimeout) params.SetNodeDialTimeout(DefaultDialTimeout) params.SetNodeStreamTimeout(DefaultStreamTimeout) p, err := pool.New(pool.NewFlatNodeParams(cfg.Addresses), user.NewAutoIDSignerRFC6979(account.PrivateKey().PrivateKey), params) if err != nil { return BasicService{}, err } err = containerID.DecodeString(cfg.ContainerID) if err != nil { return BasicService{}, errors.New("failed to decode container ID: " + err.Error()) } return BasicService{ Account: account, ContainerID: containerID, Pool: p, }, nil } // Retry is a retry mechanism for executing an action with exponential backoff. func (sfs *BasicService) Retry(action func() error) error { var ( err error backoff = InitialBackoff timer = time.NewTimer(0) ) for i := range MaxRetries { if err = action(); err == nil { return nil } if i == MaxRetries-1 { break } timer.Reset(backoff) select { case <-timer.C: case <-sfs.Ctx.Done(): return sfs.Ctx.Err() } backoff *= time.Duration(BackoffFactor) if backoff > MaxBackoff { backoff = MaxBackoff } } return err } // IsContextCanceledErr returns whether error is a wrapped [context.Canceled]. // Ref. https://github.com/nspcc-dev/neofs-sdk-go/issues/624. func IsContextCanceledErr(err error) bool { return errors.Is(err, context.Canceled) || strings.Contains(err.Error(), "context canceled") }