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.

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