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.

186 lines
6.2 KiB

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