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 }