- package http
-
- import (
- "errors"
- "fmt"
- "regexp"
- "strings"
-
- "github.com/tendermint/tendermint/light/provider"
- rpcclient "github.com/tendermint/tendermint/rpc/client"
- rpchttp "github.com/tendermint/tendermint/rpc/client/http"
- "github.com/tendermint/tendermint/types"
- )
-
- // This is very brittle, see: https://github.com/tendermint/tendermint/issues/4740
- var regexpMissingHeight = regexp.MustCompile(`height \d+ (must be less than or equal to|is not available)`)
-
- // http provider uses an RPC client to obtain the necessary information.
- type http struct {
- chainID string
- client rpcclient.RemoteClient
- }
-
- // New creates a HTTP provider, which is using the rpchttp.HTTP client under
- // the hood. If no scheme is provided in the remote URL, http will be used by
- // default.
- func New(chainID, remote string) (provider.Provider, error) {
- // Ensure URL scheme is set (default HTTP) when not provided.
- if !strings.Contains(remote, "://") {
- remote = "http://" + remote
- }
-
- httpClient, err := rpchttp.New(remote, "/websocket")
- if err != nil {
- return nil, err
- }
-
- return NewWithClient(chainID, httpClient), nil
- }
-
- // NewWithClient allows you to provide a custom client.
- func NewWithClient(chainID string, client rpcclient.RemoteClient) provider.Provider {
- return &http{
- client: client,
- chainID: chainID,
- }
- }
-
- // ChainID returns a chainID this provider was configured with.
- func (p *http) ChainID() string {
- return p.chainID
- }
-
- func (p *http) String() string {
- return fmt.Sprintf("http{%s}", p.client.Remote())
- }
-
- // SignedHeader fetches a SignedHeader at the given height and checks the
- // chainID matches.
- func (p *http) SignedHeader(height int64) (*types.SignedHeader, error) {
- h, err := validateHeight(height)
- if err != nil {
- return nil, err
- }
-
- commit, err := p.client.Commit(h)
- if err != nil {
- // TODO: standartise errors on the RPC side
- if regexpMissingHeight.MatchString(err.Error()) {
- return nil, provider.ErrSignedHeaderNotFound
- }
- return nil, err
- }
-
- if commit.Header == nil {
- return nil, errors.New("header is nil")
- }
-
- // Verify we're still on the same chain.
- if p.chainID != commit.Header.ChainID {
- return nil, fmt.Errorf("expected chainID %s, got %s", p.chainID, commit.Header.ChainID)
- }
-
- return &commit.SignedHeader, nil
- }
-
- // ValidatorSet fetches a ValidatorSet at the given height. Multiple HTTP
- // requests might be required if the validator set size is over 100.
- func (p *http) ValidatorSet(height int64) (*types.ValidatorSet, error) {
- h, err := validateHeight(height)
- if err != nil {
- return nil, err
- }
-
- maxPerPage := 100
- res, err := p.client.Validators(h, nil, &maxPerPage)
- if err != nil {
- // TODO: standartise errors on the RPC side
- if regexpMissingHeight.MatchString(err.Error()) {
- return nil, provider.ErrValidatorSetNotFound
- }
- return nil, err
- }
-
- var (
- vals = res.Validators
- page = 1
- )
-
- // Check if there are more validators.
- for len(res.Validators) == maxPerPage {
- res, err = p.client.Validators(h, &page, &maxPerPage)
- if err != nil {
- return nil, err
- }
- if len(res.Validators) > 0 {
- vals = append(vals, res.Validators...)
- }
- page++
- }
-
- return types.NewValidatorSet(vals), nil
- }
-
- // ReportEvidence calls `/broadcast_evidence` endpoint.
- func (p *http) ReportEvidence(ev types.Evidence) error {
- _, err := p.client.BroadcastEvidence(ev)
- return err
- }
-
- func validateHeight(height int64) (*int64, error) {
- if height < 0 {
- return nil, fmt.Errorf("expected height >= 0, got height %d", height)
- }
-
- h := &height
- if height == 0 {
- h = nil
- }
- return h, nil
- }
|