github.com/devwanda/aphelion-staking@v0.33.9/lite2/provider/http/http.go (about)

     1  package http
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/devwanda/aphelion-staking/lite2/provider"
    10  	rpcclient "github.com/devwanda/aphelion-staking/rpc/client"
    11  	rpchttp "github.com/devwanda/aphelion-staking/rpc/client/http"
    12  	"github.com/devwanda/aphelion-staking/types"
    13  )
    14  
    15  // This is very brittle, see: https://github.com/devwanda/aphelion-staking/issues/4740
    16  var regexpMissingHeight = regexp.MustCompile(`height \d+ (must be less than or equal to|is not available)`)
    17  
    18  // SignStatusClient combines a SignClient and StatusClient.
    19  type SignStatusClient interface {
    20  	rpcclient.SignClient
    21  	rpcclient.StatusClient
    22  	// Remote returns the remote network address in a string form.
    23  	Remote() string
    24  }
    25  
    26  // http provider uses an RPC client (or SignStatusClient more generally) to
    27  // obtain the necessary information.
    28  type http struct {
    29  	SignStatusClient // embed so interface can be converted to SignStatusClient for tests
    30  	chainID          string
    31  }
    32  
    33  // New creates a HTTP provider, which is using the rpchttp.HTTP client under the
    34  // hood. If no scheme is provided in the remote URL, http will be used by default.
    35  func New(chainID, remote string) (provider.Provider, error) {
    36  	// ensure URL scheme is set (default HTTP) when not provided
    37  	if !strings.Contains(remote, "://") {
    38  		remote = "http://" + remote
    39  	}
    40  
    41  	httpClient, err := rpchttp.New(remote, "/websocket")
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	return NewWithClient(chainID, httpClient), nil
    47  }
    48  
    49  // NewWithClient allows you to provide custom SignStatusClient.
    50  func NewWithClient(chainID string, client SignStatusClient) provider.Provider {
    51  	return &http{
    52  		SignStatusClient: client,
    53  		chainID:          chainID,
    54  	}
    55  }
    56  
    57  // ChainID returns a chainID this provider was configured with.
    58  func (p *http) ChainID() string {
    59  	return p.chainID
    60  }
    61  
    62  func (p *http) String() string {
    63  	return fmt.Sprintf("http{%s}", p.Remote())
    64  }
    65  
    66  // SignedHeader fetches a SignedHeader at the given height and checks the
    67  // chainID matches.
    68  func (p *http) SignedHeader(height int64) (*types.SignedHeader, error) {
    69  	h, err := validateHeight(height)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	commit, err := p.SignStatusClient.Commit(h)
    75  	if err != nil {
    76  		// TODO: standartise errors on the RPC side
    77  		if regexpMissingHeight.MatchString(err.Error()) {
    78  			return nil, provider.ErrSignedHeaderNotFound
    79  		}
    80  		return nil, err
    81  	}
    82  
    83  	if commit.Header == nil {
    84  		return nil, errors.New("header is nil")
    85  	}
    86  
    87  	// Verify we're still on the same chain.
    88  	if p.chainID != commit.Header.ChainID {
    89  		return nil, fmt.Errorf("expected chainID %s, got %s", p.chainID, commit.Header.ChainID)
    90  	}
    91  
    92  	return &commit.SignedHeader, nil
    93  }
    94  
    95  // ValidatorSet fetches a ValidatorSet at the given height. Multiple HTTP
    96  // requests might be required if the validator set size is over 100.
    97  func (p *http) ValidatorSet(height int64) (*types.ValidatorSet, error) {
    98  	h, err := validateHeight(height)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	const maxPerPage = 100
   104  	res, err := p.SignStatusClient.Validators(h, 0, maxPerPage)
   105  	if err != nil {
   106  		// TODO: standartise errors on the RPC side
   107  		if regexpMissingHeight.MatchString(err.Error()) {
   108  			return nil, provider.ErrValidatorSetNotFound
   109  		}
   110  		return nil, err
   111  	}
   112  
   113  	var (
   114  		vals = res.Validators
   115  		page = 1
   116  	)
   117  
   118  	// Check if there are more validators.
   119  	for len(res.Validators) == maxPerPage {
   120  		res, err = p.SignStatusClient.Validators(h, page, maxPerPage)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		if len(res.Validators) > 0 {
   125  			vals = append(vals, res.Validators...)
   126  		}
   127  		page++
   128  	}
   129  
   130  	return types.NewValidatorSet(vals), nil
   131  }
   132  
   133  func validateHeight(height int64) (*int64, error) {
   134  	if height < 0 {
   135  		return nil, fmt.Errorf("expected height >= 0, got height %d", height)
   136  	}
   137  
   138  	h := &height
   139  	if height == 0 {
   140  		h = nil
   141  	}
   142  	return h, nil
   143  }