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.

586 lines
17 KiB

  1. package statesync
  2. import (
  3. "context"
  4. "fmt"
  5. "sync"
  6. "testing"
  7. "time"
  8. "github.com/fortytw2/leaktest"
  9. "github.com/stretchr/testify/mock"
  10. "github.com/stretchr/testify/require"
  11. dbm "github.com/tendermint/tm-db"
  12. abci "github.com/tendermint/tendermint/abci/types"
  13. "github.com/tendermint/tendermint/config"
  14. "github.com/tendermint/tendermint/internal/p2p"
  15. "github.com/tendermint/tendermint/internal/statesync/mocks"
  16. "github.com/tendermint/tendermint/internal/test/factory"
  17. "github.com/tendermint/tendermint/libs/log"
  18. "github.com/tendermint/tendermint/light/provider"
  19. ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
  20. tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
  21. proxymocks "github.com/tendermint/tendermint/proxy/mocks"
  22. smmocks "github.com/tendermint/tendermint/state/mocks"
  23. "github.com/tendermint/tendermint/store"
  24. "github.com/tendermint/tendermint/types"
  25. )
  26. type reactorTestSuite struct {
  27. reactor *Reactor
  28. syncer *syncer
  29. conn *proxymocks.AppConnSnapshot
  30. connQuery *proxymocks.AppConnQuery
  31. stateProvider *mocks.StateProvider
  32. snapshotChannel *p2p.Channel
  33. snapshotInCh chan p2p.Envelope
  34. snapshotOutCh chan p2p.Envelope
  35. snapshotPeerErrCh chan p2p.PeerError
  36. chunkChannel *p2p.Channel
  37. chunkInCh chan p2p.Envelope
  38. chunkOutCh chan p2p.Envelope
  39. chunkPeerErrCh chan p2p.PeerError
  40. blockChannel *p2p.Channel
  41. blockInCh chan p2p.Envelope
  42. blockOutCh chan p2p.Envelope
  43. blockPeerErrCh chan p2p.PeerError
  44. peerUpdateCh chan p2p.PeerUpdate
  45. peerUpdates *p2p.PeerUpdates
  46. stateStore *smmocks.Store
  47. blockStore *store.BlockStore
  48. }
  49. func setup(
  50. t *testing.T,
  51. conn *proxymocks.AppConnSnapshot,
  52. connQuery *proxymocks.AppConnQuery,
  53. stateProvider *mocks.StateProvider,
  54. chBuf uint,
  55. ) *reactorTestSuite {
  56. t.Helper()
  57. if conn == nil {
  58. conn = &proxymocks.AppConnSnapshot{}
  59. }
  60. if connQuery == nil {
  61. connQuery = &proxymocks.AppConnQuery{}
  62. }
  63. if stateProvider == nil {
  64. stateProvider = &mocks.StateProvider{}
  65. }
  66. rts := &reactorTestSuite{
  67. snapshotInCh: make(chan p2p.Envelope, chBuf),
  68. snapshotOutCh: make(chan p2p.Envelope, chBuf),
  69. snapshotPeerErrCh: make(chan p2p.PeerError, chBuf),
  70. chunkInCh: make(chan p2p.Envelope, chBuf),
  71. chunkOutCh: make(chan p2p.Envelope, chBuf),
  72. chunkPeerErrCh: make(chan p2p.PeerError, chBuf),
  73. blockInCh: make(chan p2p.Envelope, chBuf),
  74. blockOutCh: make(chan p2p.Envelope, chBuf),
  75. blockPeerErrCh: make(chan p2p.PeerError, chBuf),
  76. conn: conn,
  77. connQuery: connQuery,
  78. stateProvider: stateProvider,
  79. }
  80. rts.peerUpdateCh = make(chan p2p.PeerUpdate, chBuf)
  81. rts.peerUpdates = p2p.NewPeerUpdates(rts.peerUpdateCh, int(chBuf))
  82. rts.snapshotChannel = p2p.NewChannel(
  83. SnapshotChannel,
  84. new(ssproto.Message),
  85. rts.snapshotInCh,
  86. rts.snapshotOutCh,
  87. rts.snapshotPeerErrCh,
  88. )
  89. rts.chunkChannel = p2p.NewChannel(
  90. ChunkChannel,
  91. new(ssproto.Message),
  92. rts.chunkInCh,
  93. rts.chunkOutCh,
  94. rts.chunkPeerErrCh,
  95. )
  96. rts.blockChannel = p2p.NewChannel(
  97. LightBlockChannel,
  98. new(ssproto.Message),
  99. rts.blockInCh,
  100. rts.blockOutCh,
  101. rts.blockPeerErrCh,
  102. )
  103. rts.stateStore = &smmocks.Store{}
  104. rts.blockStore = store.NewBlockStore(dbm.NewMemDB())
  105. cfg := config.DefaultStateSyncConfig()
  106. rts.reactor = NewReactor(
  107. *cfg,
  108. log.TestingLogger(),
  109. conn,
  110. connQuery,
  111. rts.snapshotChannel,
  112. rts.chunkChannel,
  113. rts.blockChannel,
  114. rts.peerUpdates,
  115. rts.stateStore,
  116. rts.blockStore,
  117. "",
  118. )
  119. // override the dispatcher with one with a shorter timeout
  120. rts.reactor.dispatcher = newDispatcher(rts.blockChannel.Out, 1*time.Second)
  121. rts.syncer = newSyncer(
  122. *cfg,
  123. log.NewNopLogger(),
  124. conn,
  125. connQuery,
  126. stateProvider,
  127. rts.snapshotOutCh,
  128. rts.chunkOutCh,
  129. "",
  130. )
  131. require.NoError(t, rts.reactor.Start())
  132. require.True(t, rts.reactor.IsRunning())
  133. t.Cleanup(func() {
  134. require.NoError(t, rts.reactor.Stop())
  135. require.False(t, rts.reactor.IsRunning())
  136. })
  137. return rts
  138. }
  139. func TestReactor_ChunkRequest_InvalidRequest(t *testing.T) {
  140. rts := setup(t, nil, nil, nil, 2)
  141. rts.chunkInCh <- p2p.Envelope{
  142. From: types.NodeID("aa"),
  143. Message: &ssproto.SnapshotsRequest{},
  144. }
  145. response := <-rts.chunkPeerErrCh
  146. require.Error(t, response.Err)
  147. require.Empty(t, rts.chunkOutCh)
  148. require.Contains(t, response.Err.Error(), "received unknown message")
  149. require.Equal(t, types.NodeID("aa"), response.NodeID)
  150. }
  151. func TestReactor_ChunkRequest(t *testing.T) {
  152. testcases := map[string]struct {
  153. request *ssproto.ChunkRequest
  154. chunk []byte
  155. expectResponse *ssproto.ChunkResponse
  156. }{
  157. "chunk is returned": {
  158. &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
  159. []byte{1, 2, 3},
  160. &ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Chunk: []byte{1, 2, 3}},
  161. },
  162. "empty chunk is returned, as empty": {
  163. &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
  164. []byte{},
  165. &ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Chunk: []byte{}},
  166. },
  167. "nil (missing) chunk is returned as missing": {
  168. &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
  169. nil,
  170. &ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Missing: true},
  171. },
  172. "invalid request": {
  173. &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
  174. nil,
  175. &ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Missing: true},
  176. },
  177. }
  178. for name, tc := range testcases {
  179. tc := tc
  180. t.Run(name, func(t *testing.T) {
  181. // mock ABCI connection to return local snapshots
  182. conn := &proxymocks.AppConnSnapshot{}
  183. conn.On("LoadSnapshotChunkSync", context.Background(), abci.RequestLoadSnapshotChunk{
  184. Height: tc.request.Height,
  185. Format: tc.request.Format,
  186. Chunk: tc.request.Index,
  187. }).Return(&abci.ResponseLoadSnapshotChunk{Chunk: tc.chunk}, nil)
  188. rts := setup(t, conn, nil, nil, 2)
  189. rts.chunkInCh <- p2p.Envelope{
  190. From: types.NodeID("aa"),
  191. Message: tc.request,
  192. }
  193. response := <-rts.chunkOutCh
  194. require.Equal(t, tc.expectResponse, response.Message)
  195. require.Empty(t, rts.chunkOutCh)
  196. conn.AssertExpectations(t)
  197. })
  198. }
  199. }
  200. func TestReactor_SnapshotsRequest_InvalidRequest(t *testing.T) {
  201. rts := setup(t, nil, nil, nil, 2)
  202. rts.snapshotInCh <- p2p.Envelope{
  203. From: types.NodeID("aa"),
  204. Message: &ssproto.ChunkRequest{},
  205. }
  206. response := <-rts.snapshotPeerErrCh
  207. require.Error(t, response.Err)
  208. require.Empty(t, rts.snapshotOutCh)
  209. require.Contains(t, response.Err.Error(), "received unknown message")
  210. require.Equal(t, types.NodeID("aa"), response.NodeID)
  211. }
  212. func TestReactor_SnapshotsRequest(t *testing.T) {
  213. testcases := map[string]struct {
  214. snapshots []*abci.Snapshot
  215. expectResponses []*ssproto.SnapshotsResponse
  216. }{
  217. "no snapshots": {nil, []*ssproto.SnapshotsResponse{}},
  218. ">10 unordered snapshots": {
  219. []*abci.Snapshot{
  220. {Height: 1, Format: 2, Chunks: 7, Hash: []byte{1, 2}, Metadata: []byte{1}},
  221. {Height: 2, Format: 2, Chunks: 7, Hash: []byte{2, 2}, Metadata: []byte{2}},
  222. {Height: 3, Format: 2, Chunks: 7, Hash: []byte{3, 2}, Metadata: []byte{3}},
  223. {Height: 1, Format: 1, Chunks: 7, Hash: []byte{1, 1}, Metadata: []byte{4}},
  224. {Height: 2, Format: 1, Chunks: 7, Hash: []byte{2, 1}, Metadata: []byte{5}},
  225. {Height: 3, Format: 1, Chunks: 7, Hash: []byte{3, 1}, Metadata: []byte{6}},
  226. {Height: 1, Format: 4, Chunks: 7, Hash: []byte{1, 4}, Metadata: []byte{7}},
  227. {Height: 2, Format: 4, Chunks: 7, Hash: []byte{2, 4}, Metadata: []byte{8}},
  228. {Height: 3, Format: 4, Chunks: 7, Hash: []byte{3, 4}, Metadata: []byte{9}},
  229. {Height: 1, Format: 3, Chunks: 7, Hash: []byte{1, 3}, Metadata: []byte{10}},
  230. {Height: 2, Format: 3, Chunks: 7, Hash: []byte{2, 3}, Metadata: []byte{11}},
  231. {Height: 3, Format: 3, Chunks: 7, Hash: []byte{3, 3}, Metadata: []byte{12}},
  232. },
  233. []*ssproto.SnapshotsResponse{
  234. {Height: 3, Format: 4, Chunks: 7, Hash: []byte{3, 4}, Metadata: []byte{9}},
  235. {Height: 3, Format: 3, Chunks: 7, Hash: []byte{3, 3}, Metadata: []byte{12}},
  236. {Height: 3, Format: 2, Chunks: 7, Hash: []byte{3, 2}, Metadata: []byte{3}},
  237. {Height: 3, Format: 1, Chunks: 7, Hash: []byte{3, 1}, Metadata: []byte{6}},
  238. {Height: 2, Format: 4, Chunks: 7, Hash: []byte{2, 4}, Metadata: []byte{8}},
  239. {Height: 2, Format: 3, Chunks: 7, Hash: []byte{2, 3}, Metadata: []byte{11}},
  240. {Height: 2, Format: 2, Chunks: 7, Hash: []byte{2, 2}, Metadata: []byte{2}},
  241. {Height: 2, Format: 1, Chunks: 7, Hash: []byte{2, 1}, Metadata: []byte{5}},
  242. {Height: 1, Format: 4, Chunks: 7, Hash: []byte{1, 4}, Metadata: []byte{7}},
  243. {Height: 1, Format: 3, Chunks: 7, Hash: []byte{1, 3}, Metadata: []byte{10}},
  244. },
  245. },
  246. }
  247. for name, tc := range testcases {
  248. tc := tc
  249. t.Run(name, func(t *testing.T) {
  250. // mock ABCI connection to return local snapshots
  251. conn := &proxymocks.AppConnSnapshot{}
  252. conn.On("ListSnapshotsSync", context.Background(), abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{
  253. Snapshots: tc.snapshots,
  254. }, nil)
  255. rts := setup(t, conn, nil, nil, 100)
  256. rts.snapshotInCh <- p2p.Envelope{
  257. From: types.NodeID("aa"),
  258. Message: &ssproto.SnapshotsRequest{},
  259. }
  260. if len(tc.expectResponses) > 0 {
  261. retryUntil(t, func() bool { return len(rts.snapshotOutCh) == len(tc.expectResponses) }, time.Second)
  262. }
  263. responses := make([]*ssproto.SnapshotsResponse, len(tc.expectResponses))
  264. for i := 0; i < len(tc.expectResponses); i++ {
  265. e := <-rts.snapshotOutCh
  266. responses[i] = e.Message.(*ssproto.SnapshotsResponse)
  267. }
  268. require.Equal(t, tc.expectResponses, responses)
  269. require.Empty(t, rts.snapshotOutCh)
  270. })
  271. }
  272. }
  273. func TestReactor_LightBlockResponse(t *testing.T) {
  274. rts := setup(t, nil, nil, nil, 2)
  275. var height int64 = 10
  276. h := factory.MakeRandomHeader()
  277. h.Height = height
  278. blockID := factory.MakeBlockIDWithHash(h.Hash())
  279. vals, pv := factory.RandValidatorSet(1, 10)
  280. vote, err := factory.MakeVote(pv[0], h.ChainID, 0, h.Height, 0, 2,
  281. blockID, factory.DefaultTestTime)
  282. require.NoError(t, err)
  283. sh := &types.SignedHeader{
  284. Header: h,
  285. Commit: &types.Commit{
  286. Height: h.Height,
  287. BlockID: blockID,
  288. Signatures: []types.CommitSig{
  289. vote.CommitSig(),
  290. },
  291. },
  292. }
  293. lb := &types.LightBlock{
  294. SignedHeader: sh,
  295. ValidatorSet: vals,
  296. }
  297. require.NoError(t, rts.blockStore.SaveSignedHeader(sh, blockID))
  298. rts.stateStore.On("LoadValidators", height).Return(vals, nil)
  299. rts.blockInCh <- p2p.Envelope{
  300. From: types.NodeID("aa"),
  301. Message: &ssproto.LightBlockRequest{
  302. Height: 10,
  303. },
  304. }
  305. require.Empty(t, rts.blockPeerErrCh)
  306. select {
  307. case response := <-rts.blockOutCh:
  308. require.Equal(t, types.NodeID("aa"), response.To)
  309. res, ok := response.Message.(*ssproto.LightBlockResponse)
  310. require.True(t, ok)
  311. receivedLB, err := types.LightBlockFromProto(res.LightBlock)
  312. require.NoError(t, err)
  313. require.Equal(t, lb, receivedLB)
  314. case <-time.After(1 * time.Second):
  315. t.Fatal("expected light block response")
  316. }
  317. }
  318. func TestReactor_Dispatcher(t *testing.T) {
  319. rts := setup(t, nil, nil, nil, 2)
  320. rts.peerUpdateCh <- p2p.PeerUpdate{
  321. NodeID: types.NodeID("aa"),
  322. Status: p2p.PeerStatusUp,
  323. }
  324. rts.peerUpdateCh <- p2p.PeerUpdate{
  325. NodeID: types.NodeID("bb"),
  326. Status: p2p.PeerStatusUp,
  327. }
  328. closeCh := make(chan struct{})
  329. defer close(closeCh)
  330. chain := buildLightBlockChain(t, 1, 10, time.Now())
  331. go handleLightBlockRequests(t, chain, rts.blockOutCh, rts.blockInCh, closeCh, 0)
  332. dispatcher := rts.reactor.Dispatcher()
  333. providers := dispatcher.Providers(factory.DefaultTestChainID, 5*time.Second)
  334. require.Len(t, providers, 2)
  335. wg := sync.WaitGroup{}
  336. for _, p := range providers {
  337. wg.Add(1)
  338. go func(t *testing.T, p provider.Provider) {
  339. defer wg.Done()
  340. for height := 2; height < 10; height++ {
  341. lb, err := p.LightBlock(context.Background(), int64(height))
  342. require.NoError(t, err)
  343. require.NotNil(t, lb)
  344. require.Equal(t, height, int(lb.Height))
  345. }
  346. }(t, p)
  347. }
  348. ctx, cancel := context.WithCancel(context.Background())
  349. go func() { wg.Wait(); cancel() }()
  350. select {
  351. case <-time.After(time.Second):
  352. // not all of the requests to the dispatcher were responded to
  353. // within the timeout
  354. t.Fail()
  355. case <-ctx.Done():
  356. }
  357. }
  358. func TestReactor_Backfill(t *testing.T) {
  359. // test backfill algorithm with varying failure rates [0, 10]
  360. failureRates := []int{0, 2, 9}
  361. for _, failureRate := range failureRates {
  362. failureRate := failureRate
  363. t.Run(fmt.Sprintf("failure rate: %d", failureRate), func(t *testing.T) {
  364. t.Cleanup(leaktest.CheckTimeout(t, 1*time.Minute))
  365. rts := setup(t, nil, nil, nil, 21)
  366. var (
  367. startHeight int64 = 20
  368. stopHeight int64 = 10
  369. stopTime = time.Date(2020, 1, 1, 0, 100, 0, 0, time.UTC)
  370. )
  371. peers := []string{"a", "b", "c", "d"}
  372. for _, peer := range peers {
  373. rts.peerUpdateCh <- p2p.PeerUpdate{
  374. NodeID: types.NodeID(peer),
  375. Status: p2p.PeerStatusUp,
  376. }
  377. }
  378. trackingHeight := startHeight
  379. rts.stateStore.On("SaveValidatorSets", mock.AnythingOfType("int64"), mock.AnythingOfType("int64"),
  380. mock.AnythingOfType("*types.ValidatorSet")).Return(func(lh, uh int64, vals *types.ValidatorSet) error {
  381. require.Equal(t, trackingHeight, lh)
  382. require.Equal(t, lh, uh)
  383. require.GreaterOrEqual(t, lh, stopHeight)
  384. trackingHeight--
  385. return nil
  386. })
  387. chain := buildLightBlockChain(t, stopHeight-1, startHeight+1, stopTime)
  388. closeCh := make(chan struct{})
  389. defer close(closeCh)
  390. go handleLightBlockRequests(t, chain, rts.blockOutCh,
  391. rts.blockInCh, closeCh, failureRate)
  392. err := rts.reactor.backfill(
  393. context.Background(),
  394. factory.DefaultTestChainID,
  395. startHeight,
  396. stopHeight,
  397. 1,
  398. factory.MakeBlockIDWithHash(chain[startHeight].Header.Hash()),
  399. stopTime,
  400. )
  401. if failureRate > 3 {
  402. require.Error(t, err)
  403. } else {
  404. require.NoError(t, err)
  405. for height := startHeight; height <= stopHeight; height++ {
  406. blockMeta := rts.blockStore.LoadBlockMeta(height)
  407. require.NotNil(t, blockMeta)
  408. }
  409. require.Nil(t, rts.blockStore.LoadBlockMeta(stopHeight-1))
  410. require.Nil(t, rts.blockStore.LoadBlockMeta(startHeight+1))
  411. }
  412. })
  413. }
  414. }
  415. // retryUntil will continue to evaluate fn and will return successfully when true
  416. // or fail when the timeout is reached.
  417. func retryUntil(t *testing.T, fn func() bool, timeout time.Duration) {
  418. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  419. defer cancel()
  420. for {
  421. if fn() {
  422. return
  423. }
  424. require.NoError(t, ctx.Err())
  425. }
  426. }
  427. func handleLightBlockRequests(t *testing.T,
  428. chain map[int64]*types.LightBlock,
  429. receiving chan p2p.Envelope,
  430. sending chan p2p.Envelope,
  431. close chan struct{},
  432. failureRate int) {
  433. requests := 0
  434. errorCount := 0
  435. for {
  436. select {
  437. case envelope := <-receiving:
  438. if msg, ok := envelope.Message.(*ssproto.LightBlockRequest); ok {
  439. if requests%10 >= failureRate {
  440. lb, err := chain[int64(msg.Height)].ToProto()
  441. require.NoError(t, err)
  442. sending <- p2p.Envelope{
  443. From: envelope.To,
  444. Message: &ssproto.LightBlockResponse{
  445. LightBlock: lb,
  446. },
  447. }
  448. } else {
  449. switch errorCount % 3 {
  450. case 0: // send a different block
  451. differntLB, err := mockLB(t, int64(msg.Height), factory.DefaultTestTime, factory.MakeBlockID()).ToProto()
  452. require.NoError(t, err)
  453. sending <- p2p.Envelope{
  454. From: envelope.To,
  455. Message: &ssproto.LightBlockResponse{
  456. LightBlock: differntLB,
  457. },
  458. }
  459. case 1: // send nil block i.e. pretend we don't have it
  460. sending <- p2p.Envelope{
  461. From: envelope.To,
  462. Message: &ssproto.LightBlockResponse{
  463. LightBlock: nil,
  464. },
  465. }
  466. case 2: // don't do anything
  467. }
  468. errorCount++
  469. }
  470. }
  471. case <-close:
  472. return
  473. }
  474. requests++
  475. }
  476. }
  477. func buildLightBlockChain(t *testing.T, fromHeight, toHeight int64, startTime time.Time) map[int64]*types.LightBlock {
  478. chain := make(map[int64]*types.LightBlock, toHeight-fromHeight)
  479. lastBlockID := factory.MakeBlockID()
  480. blockTime := startTime.Add(-5 * time.Minute)
  481. for height := fromHeight; height < toHeight; height++ {
  482. chain[height] = mockLB(t, height, blockTime, lastBlockID)
  483. lastBlockID = factory.MakeBlockIDWithHash(chain[height].Header.Hash())
  484. blockTime = blockTime.Add(1 * time.Minute)
  485. }
  486. return chain
  487. }
  488. func mockLB(t *testing.T, height int64, time time.Time,
  489. lastBlockID types.BlockID) *types.LightBlock {
  490. header, err := factory.MakeHeader(&types.Header{
  491. Height: height,
  492. LastBlockID: lastBlockID,
  493. Time: time,
  494. })
  495. require.NoError(t, err)
  496. vals, pv := factory.RandValidatorSet(3, 10)
  497. header.ValidatorsHash = vals.Hash()
  498. lastBlockID = factory.MakeBlockIDWithHash(header.Hash())
  499. voteSet := types.NewVoteSet(factory.DefaultTestChainID, height, 0, tmproto.PrecommitType, vals)
  500. commit, err := factory.MakeCommit(lastBlockID, height, 0, voteSet, pv, time)
  501. require.NoError(t, err)
  502. return &types.LightBlock{
  503. SignedHeader: &types.SignedHeader{
  504. Header: header,
  505. Commit: commit,
  506. },
  507. ValidatorSet: vals,
  508. }
  509. }