/* Package client defines a provider that uses a rpcclient to get information, which is used to get new headers and validators directly from a node. */ package client import ( "bytes" rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/lite" liteErr "github.com/tendermint/tendermint/lite/errors" ) // SignStatusClient combines a SignClient and StatusClient. type SignStatusClient interface { rpcclient.SignClient rpcclient.StatusClient } type provider struct { node SignStatusClient lastHeight int64 } // NewProvider can wrap any rpcclient to expose it as // a read-only provider. func NewProvider(node SignStatusClient) lite.Provider { return &provider{node: node} } // NewHTTPProvider can connect to a tendermint json-rpc endpoint // at the given url, and uses that as a read-only provider. func NewHTTPProvider(remote string) lite.Provider { return &provider{ node: rpcclient.NewHTTP(remote, "/websocket"), } } // StatusClient returns the internal node as a StatusClient func (p *provider) StatusClient() rpcclient.StatusClient { return p.node } // StoreCommit is a noop, as clients can only read from the chain... func (p *provider) StoreCommit(_ lite.FullCommit) error { return nil } // GetHash gets the most recent validator and sees if it matches // // TODO: improve when the rpc interface supports more functionality func (p *provider) GetByHash(hash []byte) (lite.FullCommit, error) { var fc lite.FullCommit vals, err := p.node.Validators(nil) // if we get no validators, or a different height, return an error if err != nil { return fc, err } p.updateHeight(vals.BlockHeight) vhash := types.NewValidatorSet(vals.Validators).Hash() if !bytes.Equal(hash, vhash) { return fc, liteErr.ErrCommitNotFound() } return p.seedFromVals(vals) } // GetByHeight gets the validator set by height func (p *provider) GetByHeight(h int64) (fc lite.FullCommit, err error) { commit, err := p.node.Commit(&h) if err != nil { return fc, err } return p.seedFromCommit(commit) } // LatestCommit returns the newest commit stored. func (p *provider) LatestCommit() (fc lite.FullCommit, err error) { commit, err := p.GetLatestCommit() if err != nil { return fc, err } return p.seedFromCommit(commit) } // GetLatestCommit should return the most recent commit there is, // which handles queries for future heights as per the semantics // of GetByHeight. func (p *provider) GetLatestCommit() (*ctypes.ResultCommit, error) { status, err := p.node.Status() if err != nil { return nil, err } return p.node.Commit(&status.SyncInfo.LatestBlockHeight) } // CommitFromResult ... func CommitFromResult(result *ctypes.ResultCommit) lite.Commit { return (lite.Commit)(result.SignedHeader) } func (p *provider) seedFromVals(vals *ctypes.ResultValidators) (lite.FullCommit, error) { // now get the commits and build a full commit commit, err := p.node.Commit(&vals.BlockHeight) if err != nil { return lite.FullCommit{}, err } fc := lite.NewFullCommit( CommitFromResult(commit), types.NewValidatorSet(vals.Validators), ) return fc, nil } func (p *provider) seedFromCommit(commit *ctypes.ResultCommit) (fc lite.FullCommit, err error) { fc.Commit = CommitFromResult(commit) // now get the proper validators vals, err := p.node.Validators(&commit.Header.Height) if err != nil { return fc, err } // make sure they match the commit (as we cannot enforce height) vset := types.NewValidatorSet(vals.Validators) if !bytes.Equal(vset.Hash(), commit.Header.ValidatorsHash) { return fc, liteErr.ErrValidatorsChanged() } p.updateHeight(commit.Header.Height) fc.Validators = vset return fc, nil } func (p *provider) updateHeight(h int64) { if h > p.lastHeight { p.lastHeight = h } }