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.

179 lines
5.5 KiB

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