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.

237 lines
7.9 KiB

  1. package statesync
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "time"
  7. dbm "github.com/tendermint/tm-db"
  8. tmsync "github.com/tendermint/tendermint/internal/libs/sync"
  9. "github.com/tendermint/tendermint/libs/log"
  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 %d", 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. // NewLightClientStateProviderFromDispatcher creates a light client state
  77. // provider but uses a p2p connected dispatched instead of RPC endpoints
  78. func NewLightClientStateProviderFromDispatcher(
  79. ctx context.Context,
  80. chainID string,
  81. version sm.Version,
  82. initialHeight int64,
  83. dispatcher *dispatcher,
  84. trustOptions light.TrustOptions,
  85. logger log.Logger,
  86. ) (StateProvider, error) {
  87. providers := dispatcher.Providers(chainID, 10*time.Second)
  88. if len(providers) < 2 {
  89. return nil, fmt.Errorf("at least 2 peers are required, got %d", len(providers))
  90. }
  91. providersMap := make(map[lightprovider.Provider]string)
  92. for _, p := range providers {
  93. providersMap[p] = p.(*blockProvider).String()
  94. }
  95. lc, err := light.NewClient(ctx, chainID, trustOptions, providers[0], providers[1:],
  96. lightdb.New(dbm.NewMemDB()), light.Logger(logger))
  97. if err != nil {
  98. return nil, err
  99. }
  100. return &lightClientStateProvider{
  101. lc: lc,
  102. version: version,
  103. initialHeight: initialHeight,
  104. providers: providersMap,
  105. }, nil
  106. }
  107. // AppHash implements StateProvider.
  108. func (s *lightClientStateProvider) AppHash(ctx context.Context, height uint64) ([]byte, error) {
  109. s.Lock()
  110. defer s.Unlock()
  111. // We have to fetch the next height, which contains the app hash for the previous height.
  112. header, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+1), time.Now())
  113. if err != nil {
  114. return nil, err
  115. }
  116. // We also try to fetch the blocks at height H and H+2, since we need these
  117. // when building the state while restoring the snapshot. This avoids the race
  118. // condition where we try to restore a snapshot before H+2 exists.
  119. //
  120. // FIXME This is a hack, since we can't add new methods to the interface without
  121. // breaking it. We should instead have a Has(ctx, height) method which checks
  122. // that the state provider has access to the necessary data for the height.
  123. // We piggyback on AppHash() since it's called when adding snapshots to the pool.
  124. _, err = s.lc.VerifyLightBlockAtHeight(ctx, int64(height+2), time.Now())
  125. if err != nil {
  126. return nil, err
  127. }
  128. _, err = s.lc.VerifyLightBlockAtHeight(ctx, int64(height), time.Now())
  129. if err != nil {
  130. return nil, err
  131. }
  132. return header.AppHash, nil
  133. }
  134. // Commit implements StateProvider.
  135. func (s *lightClientStateProvider) Commit(ctx context.Context, height uint64) (*types.Commit, error) {
  136. s.Lock()
  137. defer s.Unlock()
  138. header, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height), time.Now())
  139. if err != nil {
  140. return nil, err
  141. }
  142. return header.Commit, nil
  143. }
  144. // State implements StateProvider.
  145. func (s *lightClientStateProvider) State(ctx context.Context, height uint64) (sm.State, error) {
  146. s.Lock()
  147. defer s.Unlock()
  148. state := sm.State{
  149. ChainID: s.lc.ChainID(),
  150. Version: s.version,
  151. InitialHeight: s.initialHeight,
  152. }
  153. if state.InitialHeight == 0 {
  154. state.InitialHeight = 1
  155. }
  156. // The snapshot height maps onto the state heights as follows:
  157. //
  158. // height: last block, i.e. the snapshotted height
  159. // height+1: current block, i.e. the first block we'll process after the snapshot
  160. // height+2: next block, i.e. the second block after the snapshot
  161. //
  162. // We need to fetch the NextValidators from height+2 because if the application changed
  163. // the validator set at the snapshot height then this only takes effect at height+2.
  164. lastLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height), time.Now())
  165. if err != nil {
  166. return sm.State{}, err
  167. }
  168. currentLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+1), time.Now())
  169. if err != nil {
  170. return sm.State{}, err
  171. }
  172. nextLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+2), time.Now())
  173. if err != nil {
  174. return sm.State{}, err
  175. }
  176. state.LastBlockHeight = lastLightBlock.Height
  177. state.LastBlockTime = lastLightBlock.Time
  178. state.LastBlockID = lastLightBlock.Commit.BlockID
  179. state.AppHash = currentLightBlock.AppHash
  180. state.LastResultsHash = currentLightBlock.LastResultsHash
  181. state.LastValidators = lastLightBlock.ValidatorSet
  182. state.Validators = currentLightBlock.ValidatorSet
  183. state.NextValidators = nextLightBlock.ValidatorSet
  184. state.LastHeightValidatorsChanged = nextLightBlock.Height
  185. // We'll also need to fetch consensus params via RPC, using light client verification.
  186. primaryURL, ok := s.providers[s.lc.Primary()]
  187. if !ok || primaryURL == "" {
  188. return sm.State{}, fmt.Errorf("could not find address for primary light client provider")
  189. }
  190. primaryRPC, err := rpcClient(primaryURL)
  191. if err != nil {
  192. return sm.State{}, fmt.Errorf("unable to create RPC client: %w", err)
  193. }
  194. rpcclient := lightrpc.NewClient(primaryRPC, s.lc)
  195. result, err := rpcclient.ConsensusParams(ctx, &currentLightBlock.Height)
  196. if err != nil {
  197. return sm.State{}, fmt.Errorf("unable to fetch consensus parameters for height %v: %w",
  198. nextLightBlock.Height, err)
  199. }
  200. state.ConsensusParams = result.ConsensusParams
  201. state.LastHeightConsensusParamsChanged = currentLightBlock.Height
  202. return state, nil
  203. }
  204. // rpcClient sets up a new RPC client
  205. func rpcClient(server string) (*rpchttp.HTTP, error) {
  206. if !strings.Contains(server, "://") {
  207. server = "http://" + server
  208. }
  209. c, err := rpchttp.New(server)
  210. if err != nil {
  211. return nil, err
  212. }
  213. return c, nil
  214. }