- package statesync
-
- import (
- "fmt"
- "strings"
- "time"
-
- dbm "github.com/tendermint/tm-db"
-
- "github.com/tendermint/tendermint/libs/log"
- tmsync "github.com/tendermint/tendermint/libs/sync"
- "github.com/tendermint/tendermint/light"
- lightprovider "github.com/tendermint/tendermint/light/provider"
- lighthttp "github.com/tendermint/tendermint/light/provider/http"
- lightrpc "github.com/tendermint/tendermint/light/rpc"
- lightdb "github.com/tendermint/tendermint/light/store/db"
- tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
- rpchttp "github.com/tendermint/tendermint/rpc/client/http"
- sm "github.com/tendermint/tendermint/state"
- "github.com/tendermint/tendermint/types"
- )
-
- //go:generate mockery --case underscore --name StateProvider
-
- // StateProvider is a provider of trusted state data for bootstrapping a node. This refers
- // to the state.State object, not the state machine.
- type StateProvider interface {
- // AppHash returns the app hash after the given height has been committed.
- AppHash(height uint64) ([]byte, error)
- // Commit returns the commit at the given height.
- Commit(height uint64) (*types.Commit, error)
- // State returns a state object at the given height.
- State(height uint64) (sm.State, error)
- }
-
- // lightClientStateProvider is a state provider using the light client.
- type lightClientStateProvider struct {
- tmsync.Mutex // light.Client is not concurrency-safe
- lc *light.Client
- version tmstate.Version
- initialHeight int64
- providers map[lightprovider.Provider]string
- }
-
- // NewLightClientStateProvider creates a new StateProvider using a light client and RPC clients.
- func NewLightClientStateProvider(
- chainID string,
- version tmstate.Version,
- initialHeight int64,
- servers []string,
- trustOptions light.TrustOptions,
- logger log.Logger,
- ) (StateProvider, error) {
- if len(servers) < 2 {
- return nil, fmt.Errorf("at least 2 RPC servers are required, got %v", len(servers))
- }
-
- providers := make([]lightprovider.Provider, 0, len(servers))
- providerRemotes := make(map[lightprovider.Provider]string)
- for _, server := range servers {
- client, err := rpcClient(server)
- if err != nil {
- return nil, fmt.Errorf("failed to set up RPC client: %w", err)
- }
- provider := lighthttp.NewWithClient(chainID, client)
- providers = append(providers, provider)
- // We store the RPC addresses keyed by provider, so we can find the address of the primary
- // provider used by the light client and use it to fetch consensus parameters.
- providerRemotes[provider] = server
- }
-
- lc, err := light.NewClient(chainID, trustOptions, providers[0], providers[1:],
- lightdb.New(dbm.NewMemDB(), ""), light.Logger(logger), light.MaxRetryAttempts(5))
- if err != nil {
- return nil, err
- }
- return &lightClientStateProvider{
- lc: lc,
- version: version,
- initialHeight: initialHeight,
- providers: providerRemotes,
- }, nil
- }
-
- // AppHash implements StateProvider.
- func (s *lightClientStateProvider) AppHash(height uint64) ([]byte, error) {
- s.Lock()
- defer s.Unlock()
-
- // We have to fetch the next height, which contains the app hash for the previous height.
- header, err := s.lc.VerifyHeaderAtHeight(int64(height+1), time.Now())
- if err != nil {
- return nil, err
- }
- return header.AppHash, nil
- }
-
- // Commit implements StateProvider.
- func (s *lightClientStateProvider) Commit(height uint64) (*types.Commit, error) {
- s.Lock()
- defer s.Unlock()
- header, err := s.lc.VerifyHeaderAtHeight(int64(height), time.Now())
- if err != nil {
- return nil, err
- }
- return header.Commit, nil
- }
-
- // State implements StateProvider.
- func (s *lightClientStateProvider) State(height uint64) (sm.State, error) {
- s.Lock()
- defer s.Unlock()
-
- state := sm.State{
- ChainID: s.lc.ChainID(),
- Version: s.version,
- InitialHeight: s.initialHeight,
- }
- if state.InitialHeight == 0 {
- state.InitialHeight = 1
- }
-
- // We need to verify up until h+2, to get the validator set. This also prefetches the headers
- // for h and h+1 in the typical case where the trusted header is after the snapshot height.
- _, err := s.lc.VerifyHeaderAtHeight(int64(height+2), time.Now())
- if err != nil {
- return sm.State{}, err
- }
- header, err := s.lc.VerifyHeaderAtHeight(int64(height), time.Now())
- if err != nil {
- return sm.State{}, err
- }
- nextHeader, err := s.lc.VerifyHeaderAtHeight(int64(height+1), time.Now())
- if err != nil {
- return sm.State{}, err
- }
- state.LastBlockHeight = header.Height
- state.LastBlockTime = header.Time
- state.LastBlockID = header.Commit.BlockID
- state.AppHash = nextHeader.AppHash
- state.LastResultsHash = nextHeader.LastResultsHash
-
- state.LastValidators, _, err = s.lc.TrustedValidatorSet(int64(height))
- if err != nil {
- return sm.State{}, err
- }
- state.Validators, _, err = s.lc.TrustedValidatorSet(int64(height + 1))
- if err != nil {
- return sm.State{}, err
- }
- state.NextValidators, _, err = s.lc.TrustedValidatorSet(int64(height + 2))
- if err != nil {
- return sm.State{}, err
- }
- state.LastHeightValidatorsChanged = int64(height)
-
- // We'll also need to fetch consensus params via RPC, using light client verification.
- primaryURL, ok := s.providers[s.lc.Primary()]
- if !ok || primaryURL == "" {
- return sm.State{}, fmt.Errorf("could not find address for primary light client provider")
- }
- primaryRPC, err := rpcClient(primaryURL)
- if err != nil {
- return sm.State{}, fmt.Errorf("unable to create RPC client: %w", err)
- }
- rpcclient := lightrpc.NewClient(primaryRPC, s.lc)
- result, err := rpcclient.ConsensusParams(&nextHeader.Height)
- if err != nil {
- return sm.State{}, fmt.Errorf("unable to fetch consensus parameters for height %v: %w",
- nextHeader.Height, err)
- }
- state.ConsensusParams = result.ConsensusParams
-
- return state, nil
- }
-
- // rpcClient sets up a new RPC client
- func rpcClient(server string) (*rpchttp.HTTP, error) {
- if !strings.Contains(server, "://") {
- server = "http://" + server
- }
- c, err := rpchttp.New(server, "/websocket")
- if err != nil {
- return nil, err
- }
- return c, nil
- }
|