You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
5.6 KiB

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