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 }