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.

180 lines
5.7 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. providers map[lightprovider.Provider]string
  36. }
  37. // NewLightClientStateProvider creates a new StateProvider using a light client and RPC clients.
  38. func NewLightClientStateProvider(
  39. chainID string,
  40. version tmstate.Version,
  41. servers []string,
  42. trustOptions light.TrustOptions,
  43. logger log.Logger,
  44. ) (StateProvider, error) {
  45. if len(servers) < 2 {
  46. return nil, fmt.Errorf("at least 2 RPC servers are required, got %v", len(servers))
  47. }
  48. providers := make([]lightprovider.Provider, 0, len(servers))
  49. providerRemotes := make(map[lightprovider.Provider]string)
  50. for _, server := range servers {
  51. client, err := rpcClient(server)
  52. if err != nil {
  53. return nil, fmt.Errorf("failed to set up RPC client: %w", err)
  54. }
  55. provider := lighthttp.NewWithClient(chainID, client)
  56. providers = append(providers, provider)
  57. // We store the RPC addresses keyed by provider, so we can find the address of the primary
  58. // provider used by the light client and use it to fetch consensus parameters.
  59. providerRemotes[provider] = server
  60. }
  61. lc, err := light.NewClient(chainID, trustOptions, providers[0], providers[1:],
  62. lightdb.New(dbm.NewMemDB(), ""), light.Logger(logger), light.MaxRetryAttempts(5))
  63. if err != nil {
  64. return nil, err
  65. }
  66. return &lightClientStateProvider{
  67. lc: lc,
  68. version: version,
  69. providers: providerRemotes,
  70. }, nil
  71. }
  72. // AppHash implements StateProvider.
  73. func (s *lightClientStateProvider) AppHash(height uint64) ([]byte, error) {
  74. s.Lock()
  75. defer s.Unlock()
  76. // We have to fetch the next height, which contains the app hash for the previous height.
  77. header, err := s.lc.VerifyHeaderAtHeight(int64(height+1), time.Now())
  78. if err != nil {
  79. return nil, err
  80. }
  81. return header.AppHash, nil
  82. }
  83. // Commit implements StateProvider.
  84. func (s *lightClientStateProvider) Commit(height uint64) (*types.Commit, error) {
  85. s.Lock()
  86. defer s.Unlock()
  87. header, err := s.lc.VerifyHeaderAtHeight(int64(height), time.Now())
  88. if err != nil {
  89. return nil, err
  90. }
  91. return header.Commit, nil
  92. }
  93. // State implements StateProvider.
  94. func (s *lightClientStateProvider) State(height uint64) (sm.State, error) {
  95. s.Lock()
  96. defer s.Unlock()
  97. state := sm.State{
  98. ChainID: s.lc.ChainID(),
  99. Version: s.version,
  100. }
  101. // We need to verify up until h+2, to get the validator set. This also prefetches the headers
  102. // for h and h+1 in the typical case where the trusted header is after the snapshot height.
  103. _, err := s.lc.VerifyHeaderAtHeight(int64(height+2), time.Now())
  104. if err != nil {
  105. return sm.State{}, err
  106. }
  107. header, err := s.lc.VerifyHeaderAtHeight(int64(height), time.Now())
  108. if err != nil {
  109. return sm.State{}, err
  110. }
  111. nextHeader, err := s.lc.VerifyHeaderAtHeight(int64(height+1), time.Now())
  112. if err != nil {
  113. return sm.State{}, err
  114. }
  115. state.LastBlockHeight = header.Height
  116. state.LastBlockTime = header.Time
  117. state.LastBlockID = header.Commit.BlockID
  118. state.AppHash = nextHeader.AppHash
  119. state.LastResultsHash = nextHeader.LastResultsHash
  120. state.LastValidators, _, err = s.lc.TrustedValidatorSet(int64(height))
  121. if err != nil {
  122. return sm.State{}, err
  123. }
  124. state.Validators, _, err = s.lc.TrustedValidatorSet(int64(height + 1))
  125. if err != nil {
  126. return sm.State{}, err
  127. }
  128. state.NextValidators, _, err = s.lc.TrustedValidatorSet(int64(height + 2))
  129. if err != nil {
  130. return sm.State{}, err
  131. }
  132. state.LastHeightValidatorsChanged = int64(height)
  133. // We'll also need to fetch consensus params via RPC, using light client verification.
  134. primaryURL, ok := s.providers[s.lc.Primary()]
  135. if !ok || primaryURL == "" {
  136. return sm.State{}, fmt.Errorf("could not find address for primary light client provider")
  137. }
  138. primaryRPC, err := rpcClient(primaryURL)
  139. if err != nil {
  140. return sm.State{}, fmt.Errorf("unable to create RPC client: %w", err)
  141. }
  142. rpcclient := lightrpc.NewClient(primaryRPC, s.lc)
  143. result, err := rpcclient.ConsensusParams(&nextHeader.Height)
  144. if err != nil {
  145. return sm.State{}, fmt.Errorf("unable to fetch consensus parameters for height %v: %w",
  146. nextHeader.Height, err)
  147. }
  148. state.ConsensusParams = result.ConsensusParams
  149. return state, nil
  150. }
  151. // rpcClient sets up a new RPC client
  152. func rpcClient(server string) (*rpchttp.HTTP, error) {
  153. if !strings.Contains(server, "://") {
  154. server = "http://" + server
  155. }
  156. c, err := rpchttp.New(server, "/websocket")
  157. if err != nil {
  158. return nil, err
  159. }
  160. return c, nil
  161. }