github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/lite2/provider/http/http.go (about)

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