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.

202 lines
6.9 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. 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(ctx context.Context, height uint64) ([]byte, error)
  25. // Commit returns the commit at the given height.
  26. Commit(ctx context.Context, height uint64) (*types.Commit, error)
  27. // State returns a state object at the given height.
  28. State(ctx context.Context, 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 sm.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. ctx context.Context,
  41. chainID string,
  42. version sm.Version,
  43. initialHeight int64,
  44. servers []string,
  45. trustOptions light.TrustOptions,
  46. logger log.Logger,
  47. ) (StateProvider, error) {
  48. if len(servers) < 2 {
  49. return nil, fmt.Errorf("at least 2 RPC servers are required, got %v", len(servers))
  50. }
  51. providers := make([]lightprovider.Provider, 0, len(servers))
  52. providerRemotes := make(map[lightprovider.Provider]string)
  53. for _, server := range servers {
  54. client, err := rpcClient(server)
  55. if err != nil {
  56. return nil, fmt.Errorf("failed to set up RPC client: %w", err)
  57. }
  58. provider := lighthttp.NewWithClient(chainID, client)
  59. providers = append(providers, provider)
  60. // We store the RPC addresses keyed by provider, so we can find the address of the primary
  61. // provider used by the light client and use it to fetch consensus parameters.
  62. providerRemotes[provider] = server
  63. }
  64. lc, err := light.NewClient(ctx, chainID, trustOptions, providers[0], providers[1:],
  65. lightdb.New(dbm.NewMemDB()), light.Logger(logger))
  66. if err != nil {
  67. return nil, err
  68. }
  69. return &lightClientStateProvider{
  70. lc: lc,
  71. version: version,
  72. initialHeight: initialHeight,
  73. providers: providerRemotes,
  74. }, nil
  75. }
  76. // AppHash implements StateProvider.
  77. func (s *lightClientStateProvider) AppHash(ctx context.Context, height uint64) ([]byte, error) {
  78. s.Lock()
  79. defer s.Unlock()
  80. // We have to fetch the next height, which contains the app hash for the previous height.
  81. header, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+1), time.Now())
  82. if err != nil {
  83. return nil, err
  84. }
  85. // We also try to fetch the blocks at height H and H+2, since we need these
  86. // when building the state while restoring the snapshot. This avoids the race
  87. // condition where we try to restore a snapshot before H+2 exists.
  88. //
  89. // FIXME This is a hack, since we can't add new methods to the interface without
  90. // breaking it. We should instead have a Has(ctx, height) method which checks
  91. // that the state provider has access to the necessary data for the height.
  92. // We piggyback on AppHash() since it's called when adding snapshots to the pool.
  93. _, err = s.lc.VerifyLightBlockAtHeight(ctx, int64(height+2), time.Now())
  94. if err != nil {
  95. return nil, err
  96. }
  97. _, err = s.lc.VerifyLightBlockAtHeight(ctx, int64(height), time.Now())
  98. if err != nil {
  99. return nil, err
  100. }
  101. return header.AppHash, nil
  102. }
  103. // Commit implements StateProvider.
  104. func (s *lightClientStateProvider) Commit(ctx context.Context, height uint64) (*types.Commit, error) {
  105. s.Lock()
  106. defer s.Unlock()
  107. header, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height), time.Now())
  108. if err != nil {
  109. return nil, err
  110. }
  111. return header.Commit, nil
  112. }
  113. // State implements StateProvider.
  114. func (s *lightClientStateProvider) State(ctx context.Context, height uint64) (sm.State, error) {
  115. s.Lock()
  116. defer s.Unlock()
  117. state := sm.State{
  118. ChainID: s.lc.ChainID(),
  119. Version: s.version,
  120. InitialHeight: s.initialHeight,
  121. }
  122. if state.InitialHeight == 0 {
  123. state.InitialHeight = 1
  124. }
  125. // The snapshot height maps onto the state heights as follows:
  126. //
  127. // height: last block, i.e. the snapshotted height
  128. // height+1: current block, i.e. the first block we'll process after the snapshot
  129. // height+2: next block, i.e. the second block after the snapshot
  130. //
  131. // We need to fetch the NextValidators from height+2 because if the application changed
  132. // the validator set at the snapshot height then this only takes effect at height+2.
  133. lastLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height), time.Now())
  134. if err != nil {
  135. return sm.State{}, err
  136. }
  137. currentLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+1), time.Now())
  138. if err != nil {
  139. return sm.State{}, err
  140. }
  141. nextLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+2), time.Now())
  142. if err != nil {
  143. return sm.State{}, err
  144. }
  145. state.LastBlockHeight = lastLightBlock.Height
  146. state.LastBlockTime = lastLightBlock.Time
  147. state.LastBlockID = lastLightBlock.Commit.BlockID
  148. state.AppHash = currentLightBlock.AppHash
  149. state.LastResultsHash = currentLightBlock.LastResultsHash
  150. state.LastValidators = lastLightBlock.ValidatorSet
  151. state.Validators = currentLightBlock.ValidatorSet
  152. state.NextValidators = nextLightBlock.ValidatorSet
  153. state.LastHeightValidatorsChanged = nextLightBlock.Height
  154. // We'll also need to fetch consensus params via RPC, using light client verification.
  155. primaryURL, ok := s.providers[s.lc.Primary()]
  156. if !ok || primaryURL == "" {
  157. return sm.State{}, fmt.Errorf("could not find address for primary light client provider")
  158. }
  159. primaryRPC, err := rpcClient(primaryURL)
  160. if err != nil {
  161. return sm.State{}, fmt.Errorf("unable to create RPC client: %w", err)
  162. }
  163. rpcclient := lightrpc.NewClient(primaryRPC, s.lc)
  164. result, err := rpcclient.ConsensusParams(ctx, &currentLightBlock.Height)
  165. if err != nil {
  166. return sm.State{}, fmt.Errorf("unable to fetch consensus parameters for height %v: %w",
  167. nextLightBlock.Height, err)
  168. }
  169. state.ConsensusParams = result.ConsensusParams
  170. state.LastHeightConsensusParamsChanged = currentLightBlock.Height
  171. return state, nil
  172. }
  173. // rpcClient sets up a new RPC client
  174. func rpcClient(server string) (*rpchttp.HTTP, error) {
  175. if !strings.Contains(server, "://") {
  176. server = "http://" + server
  177. }
  178. c, err := rpchttp.New(server, "/websocket")
  179. if err != nil {
  180. return nil, err
  181. }
  182. return c, nil
  183. }