package privval import ( "context" "fmt" "time" "github.com/tendermint/tendermint/crypto" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" ) // RetrySignerClient wraps SignerClient adding retry for each operation (except // Ping) w/ a timeout. type RetrySignerClient struct { next *SignerClient retries int timeout time.Duration } // NewRetrySignerClient returns RetrySignerClient. If +retries+ is 0, the // client will be retrying each operation indefinitely. func NewRetrySignerClient(sc *SignerClient, retries int, timeout time.Duration) *RetrySignerClient { return &RetrySignerClient{sc, retries, timeout} } var _ types.PrivValidator = (*RetrySignerClient)(nil) func (sc *RetrySignerClient) Close() error { return sc.next.Close() } func (sc *RetrySignerClient) IsConnected() bool { return sc.next.IsConnected() } func (sc *RetrySignerClient) WaitForConnection(ctx context.Context, maxWait time.Duration) error { return sc.next.WaitForConnection(ctx, maxWait) } //-------------------------------------------------------- // Implement PrivValidator func (sc *RetrySignerClient) Ping(ctx context.Context) error { return sc.next.Ping(ctx) } func (sc *RetrySignerClient) GetPubKey(ctx context.Context) (crypto.PubKey, error) { var ( pk crypto.PubKey err error ) t := time.NewTimer(sc.timeout) for i := 0; i < sc.retries || sc.retries == 0; i++ { pk, err = sc.next.GetPubKey(ctx) if err == nil { return pk, nil } // If remote signer errors, we don't retry. if _, ok := err.(*RemoteSignerError); ok { return nil, err } select { case <-ctx.Done(): return nil, ctx.Err() case <-t.C: t.Reset(sc.timeout) } } return nil, fmt.Errorf("exhausted all attempts to get pubkey: %w", err) } func (sc *RetrySignerClient) SignVote(ctx context.Context, chainID string, vote *tmproto.Vote) error { var err error for i := 0; i < sc.retries || sc.retries == 0; i++ { err = sc.next.SignVote(ctx, chainID, vote) if err == nil { return nil } // If remote signer errors, we don't retry. if _, ok := err.(*RemoteSignerError); ok { return err } time.Sleep(sc.timeout) } return fmt.Errorf("exhausted all attempts to sign vote: %w", err) } func (sc *RetrySignerClient) SignProposal(ctx context.Context, chainID string, proposal *tmproto.Proposal) error { var err error for i := 0; i < sc.retries || sc.retries == 0; i++ { err = sc.next.SignProposal(ctx, chainID, proposal) if err == nil { return nil } // If remote signer errors, we don't retry. if _, ok := err.(*RemoteSignerError); ok { return err } time.Sleep(sc.timeout) } return fmt.Errorf("exhausted all attempts to sign proposal: %w", err) }