github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/statesync/stateprovider.go (about)

     1  package statesync
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	dbm "github.com/tendermint/tm-db"
     9  
    10  	"github.com/tendermint/tendermint/libs/log"
    11  	tmsync "github.com/tendermint/tendermint/libs/sync"
    12  	"github.com/tendermint/tendermint/light"
    13  	lightprovider "github.com/tendermint/tendermint/light/provider"
    14  	lighthttp "github.com/tendermint/tendermint/light/provider/http"
    15  	lightrpc "github.com/tendermint/tendermint/light/rpc"
    16  	lightdb "github.com/tendermint/tendermint/light/store/db"
    17  	tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
    18  	rpchttp "github.com/tendermint/tendermint/rpc/client/http"
    19  	sm "github.com/tendermint/tendermint/state"
    20  	"github.com/tendermint/tendermint/types"
    21  )
    22  
    23  //go:generate mockery -case underscore -name StateProvider
    24  
    25  // StateProvider is a provider of trusted state data for bootstrapping a node. This refers
    26  // to the state.State object, not the state machine.
    27  type StateProvider interface {
    28  	// AppHash returns the app hash after the given height has been committed.
    29  	AppHash(height uint64) ([]byte, error)
    30  	// Commit returns the commit at the given height.
    31  	Commit(height uint64) (*types.Commit, error)
    32  	// State returns a state object at the given height.
    33  	State(height uint64) (sm.State, error)
    34  }
    35  
    36  // lightClientStateProvider is a state provider using the light client.
    37  type lightClientStateProvider struct {
    38  	tmsync.Mutex // light.Client is not concurrency-safe
    39  	lc           *light.Client
    40  	version      tmstate.Version
    41  	providers    map[lightprovider.Provider]string
    42  }
    43  
    44  // NewLightClientStateProvider creates a new StateProvider using a light client and RPC clients.
    45  func NewLightClientStateProvider(
    46  	chainID string,
    47  	version tmstate.Version,
    48  	servers []string,
    49  	trustOptions light.TrustOptions,
    50  	logger log.Logger,
    51  ) (StateProvider, error) {
    52  	if len(servers) < 2 {
    53  		return nil, fmt.Errorf("at least 2 RPC servers are required, got %v", len(servers))
    54  	}
    55  
    56  	providers := make([]lightprovider.Provider, 0, len(servers))
    57  	providerRemotes := make(map[lightprovider.Provider]string)
    58  	for _, server := range servers {
    59  		client, err := rpcClient(server)
    60  		if err != nil {
    61  			return nil, fmt.Errorf("failed to set up RPC client: %w", err)
    62  		}
    63  		provider := lighthttp.NewWithClient(chainID, client)
    64  		providers = append(providers, provider)
    65  		// We store the RPC addresses keyed by provider, so we can find the address of the primary
    66  		// provider used by the light client and use it to fetch consensus parameters.
    67  		providerRemotes[provider] = server
    68  	}
    69  
    70  	lc, err := light.NewClient(chainID, trustOptions, providers[0], providers[1:],
    71  		lightdb.New(dbm.NewMemDB(), ""), light.Logger(logger), light.MaxRetryAttempts(5))
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	return &lightClientStateProvider{
    76  		lc:        lc,
    77  		version:   version,
    78  		providers: providerRemotes,
    79  	}, nil
    80  }
    81  
    82  // AppHash implements StateProvider.
    83  func (s *lightClientStateProvider) AppHash(height uint64) ([]byte, error) {
    84  	s.Lock()
    85  	defer s.Unlock()
    86  
    87  	// We have to fetch the next height, which contains the app hash for the previous height.
    88  	header, err := s.lc.VerifyHeaderAtHeight(int64(height+1), time.Now())
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	return header.AppHash, nil
    93  }
    94  
    95  // Commit implements StateProvider.
    96  func (s *lightClientStateProvider) Commit(height uint64) (*types.Commit, error) {
    97  	s.Lock()
    98  	defer s.Unlock()
    99  	header, err := s.lc.VerifyHeaderAtHeight(int64(height), time.Now())
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	return header.Commit, nil
   104  }
   105  
   106  // State implements StateProvider.
   107  func (s *lightClientStateProvider) State(height uint64) (sm.State, error) {
   108  	s.Lock()
   109  	defer s.Unlock()
   110  
   111  	state := sm.State{
   112  		ChainID: s.lc.ChainID(),
   113  		Version: s.version,
   114  	}
   115  
   116  	// We need to verify up until h+2, to get the validator set. This also prefetches the headers
   117  	// for h and h+1 in the typical case where the trusted header is after the snapshot height.
   118  	_, err := s.lc.VerifyHeaderAtHeight(int64(height+2), time.Now())
   119  	if err != nil {
   120  		return sm.State{}, err
   121  	}
   122  	header, err := s.lc.VerifyHeaderAtHeight(int64(height), time.Now())
   123  	if err != nil {
   124  		return sm.State{}, err
   125  	}
   126  	nextHeader, err := s.lc.VerifyHeaderAtHeight(int64(height+1), time.Now())
   127  	if err != nil {
   128  		return sm.State{}, err
   129  	}
   130  	state.LastBlockHeight = header.Height
   131  	state.LastBlockTime = header.Time
   132  	state.LastBlockID = header.Commit.BlockID
   133  	state.AppHash = nextHeader.AppHash
   134  	state.LastResultsHash = nextHeader.LastResultsHash
   135  
   136  	state.LastValidators, _, err = s.lc.TrustedValidatorSet(int64(height))
   137  	if err != nil {
   138  		return sm.State{}, err
   139  	}
   140  	state.Validators, _, err = s.lc.TrustedValidatorSet(int64(height + 1))
   141  	if err != nil {
   142  		return sm.State{}, err
   143  	}
   144  	state.NextValidators, _, err = s.lc.TrustedValidatorSet(int64(height + 2))
   145  	if err != nil {
   146  		return sm.State{}, err
   147  	}
   148  	state.LastHeightValidatorsChanged = int64(height)
   149  
   150  	// We'll also need to fetch consensus params via RPC, using light client verification.
   151  	primaryURL, ok := s.providers[s.lc.Primary()]
   152  	if !ok || primaryURL == "" {
   153  		return sm.State{}, fmt.Errorf("could not find address for primary light client provider")
   154  	}
   155  	primaryRPC, err := rpcClient(primaryURL)
   156  	if err != nil {
   157  		return sm.State{}, fmt.Errorf("unable to create RPC client: %w", err)
   158  	}
   159  	rpcclient := lightrpc.NewClient(primaryRPC, s.lc)
   160  	result, err := rpcclient.ConsensusParams(&nextHeader.Height)
   161  	if err != nil {
   162  		return sm.State{}, fmt.Errorf("unable to fetch consensus parameters for height %v: %w",
   163  			nextHeader.Height, err)
   164  	}
   165  	state.ConsensusParams = result.ConsensusParams
   166  
   167  	return state, nil
   168  }
   169  
   170  // rpcClient sets up a new RPC client
   171  func rpcClient(server string) (*rpchttp.HTTP, error) {
   172  	if !strings.Contains(server, "://") {
   173  		server = "http://" + server
   174  	}
   175  	c, err := rpchttp.New(server, "/websocket")
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	return c, nil
   180  }