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.

286 lines
8.3 KiB

  1. package statesync
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "github.com/stretchr/testify/require"
  7. abci "github.com/tendermint/tendermint/abci/types"
  8. "github.com/tendermint/tendermint/libs/log"
  9. "github.com/tendermint/tendermint/p2p"
  10. ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
  11. proxymocks "github.com/tendermint/tendermint/proxy/mocks"
  12. "github.com/tendermint/tendermint/statesync/mocks"
  13. )
  14. type reactorTestSuite struct {
  15. reactor *Reactor
  16. syncer *syncer
  17. conn *proxymocks.AppConnSnapshot
  18. connQuery *proxymocks.AppConnQuery
  19. stateProvider *mocks.StateProvider
  20. snapshotChannel *p2p.Channel
  21. snapshotInCh chan p2p.Envelope
  22. snapshotOutCh chan p2p.Envelope
  23. snapshotPeerErrCh chan p2p.PeerError
  24. chunkChannel *p2p.Channel
  25. chunkInCh chan p2p.Envelope
  26. chunkOutCh chan p2p.Envelope
  27. chunkPeerErrCh chan p2p.PeerError
  28. peerUpdates *p2p.PeerUpdatesCh
  29. }
  30. func setup(
  31. t *testing.T,
  32. conn *proxymocks.AppConnSnapshot,
  33. connQuery *proxymocks.AppConnQuery,
  34. stateProvider *mocks.StateProvider,
  35. chBuf uint,
  36. ) *reactorTestSuite {
  37. t.Helper()
  38. if conn == nil {
  39. conn = &proxymocks.AppConnSnapshot{}
  40. }
  41. if connQuery == nil {
  42. connQuery = &proxymocks.AppConnQuery{}
  43. }
  44. if stateProvider == nil {
  45. stateProvider = &mocks.StateProvider{}
  46. }
  47. rts := &reactorTestSuite{
  48. snapshotInCh: make(chan p2p.Envelope, chBuf),
  49. snapshotOutCh: make(chan p2p.Envelope, chBuf),
  50. snapshotPeerErrCh: make(chan p2p.PeerError, chBuf),
  51. chunkInCh: make(chan p2p.Envelope, chBuf),
  52. chunkOutCh: make(chan p2p.Envelope, chBuf),
  53. chunkPeerErrCh: make(chan p2p.PeerError, chBuf),
  54. peerUpdates: p2p.NewPeerUpdates(),
  55. conn: conn,
  56. connQuery: connQuery,
  57. stateProvider: stateProvider,
  58. }
  59. rts.snapshotChannel = p2p.NewChannel(
  60. SnapshotChannel,
  61. new(ssproto.Message),
  62. rts.snapshotInCh,
  63. rts.snapshotOutCh,
  64. rts.snapshotPeerErrCh,
  65. )
  66. rts.chunkChannel = p2p.NewChannel(
  67. ChunkChannel,
  68. new(ssproto.Message),
  69. rts.chunkInCh,
  70. rts.chunkOutCh,
  71. rts.chunkPeerErrCh,
  72. )
  73. rts.reactor = NewReactor(
  74. log.NewNopLogger(),
  75. conn,
  76. connQuery,
  77. rts.snapshotChannel,
  78. rts.chunkChannel,
  79. rts.peerUpdates,
  80. "",
  81. )
  82. rts.syncer = newSyncer(
  83. log.NewNopLogger(),
  84. conn,
  85. connQuery,
  86. stateProvider,
  87. rts.snapshotOutCh,
  88. rts.chunkOutCh,
  89. "",
  90. )
  91. require.NoError(t, rts.reactor.Start())
  92. require.True(t, rts.reactor.IsRunning())
  93. t.Cleanup(func() {
  94. require.NoError(t, rts.reactor.Stop())
  95. require.False(t, rts.reactor.IsRunning())
  96. })
  97. return rts
  98. }
  99. func TestReactor_ChunkRequest_InvalidRequest(t *testing.T) {
  100. rts := setup(t, nil, nil, nil, 2)
  101. rts.chunkInCh <- p2p.Envelope{
  102. From: p2p.PeerID{0xAA},
  103. Message: &ssproto.SnapshotsRequest{},
  104. }
  105. response := <-rts.chunkPeerErrCh
  106. require.Error(t, response.Err)
  107. require.Empty(t, rts.chunkOutCh)
  108. require.Contains(t, response.Err.Error(), "received unknown message")
  109. require.Equal(t, p2p.PeerID{0xAA}, response.PeerID)
  110. }
  111. func TestReactor_ChunkRequest(t *testing.T) {
  112. testcases := map[string]struct {
  113. request *ssproto.ChunkRequest
  114. chunk []byte
  115. expectResponse *ssproto.ChunkResponse
  116. }{
  117. "chunk is returned": {
  118. &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
  119. []byte{1, 2, 3},
  120. &ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Chunk: []byte{1, 2, 3}},
  121. },
  122. "empty chunk is returned, as empty": {
  123. &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
  124. []byte{},
  125. &ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Chunk: []byte{}},
  126. },
  127. "nil (missing) chunk is returned as missing": {
  128. &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
  129. nil,
  130. &ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Missing: true},
  131. },
  132. "invalid request": {
  133. &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
  134. nil,
  135. &ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Missing: true},
  136. },
  137. }
  138. for name, tc := range testcases {
  139. tc := tc
  140. t.Run(name, func(t *testing.T) {
  141. // mock ABCI connection to return local snapshots
  142. conn := &proxymocks.AppConnSnapshot{}
  143. conn.On("LoadSnapshotChunkSync", context.Background(), abci.RequestLoadSnapshotChunk{
  144. Height: tc.request.Height,
  145. Format: tc.request.Format,
  146. Chunk: tc.request.Index,
  147. }).Return(&abci.ResponseLoadSnapshotChunk{Chunk: tc.chunk}, nil)
  148. rts := setup(t, conn, nil, nil, 2)
  149. rts.chunkInCh <- p2p.Envelope{
  150. From: p2p.PeerID{0xAA},
  151. Message: tc.request,
  152. }
  153. response := <-rts.chunkOutCh
  154. require.Equal(t, tc.expectResponse, response.Message)
  155. require.Empty(t, rts.chunkOutCh)
  156. conn.AssertExpectations(t)
  157. })
  158. }
  159. }
  160. func TestReactor_SnapshotsRequest_InvalidRequest(t *testing.T) {
  161. rts := setup(t, nil, nil, nil, 2)
  162. rts.snapshotInCh <- p2p.Envelope{
  163. From: p2p.PeerID{0xAA},
  164. Message: &ssproto.ChunkRequest{},
  165. }
  166. response := <-rts.snapshotPeerErrCh
  167. require.Error(t, response.Err)
  168. require.Empty(t, rts.snapshotOutCh)
  169. require.Contains(t, response.Err.Error(), "received unknown message")
  170. require.Equal(t, p2p.PeerID{0xAA}, response.PeerID)
  171. }
  172. func TestReactor_SnapshotsRequest(t *testing.T) {
  173. testcases := map[string]struct {
  174. snapshots []*abci.Snapshot
  175. expectResponses []*ssproto.SnapshotsResponse
  176. }{
  177. "no snapshots": {nil, []*ssproto.SnapshotsResponse{}},
  178. ">10 unordered snapshots": {
  179. []*abci.Snapshot{
  180. {Height: 1, Format: 2, Chunks: 7, Hash: []byte{1, 2}, Metadata: []byte{1}},
  181. {Height: 2, Format: 2, Chunks: 7, Hash: []byte{2, 2}, Metadata: []byte{2}},
  182. {Height: 3, Format: 2, Chunks: 7, Hash: []byte{3, 2}, Metadata: []byte{3}},
  183. {Height: 1, Format: 1, Chunks: 7, Hash: []byte{1, 1}, Metadata: []byte{4}},
  184. {Height: 2, Format: 1, Chunks: 7, Hash: []byte{2, 1}, Metadata: []byte{5}},
  185. {Height: 3, Format: 1, Chunks: 7, Hash: []byte{3, 1}, Metadata: []byte{6}},
  186. {Height: 1, Format: 4, Chunks: 7, Hash: []byte{1, 4}, Metadata: []byte{7}},
  187. {Height: 2, Format: 4, Chunks: 7, Hash: []byte{2, 4}, Metadata: []byte{8}},
  188. {Height: 3, Format: 4, Chunks: 7, Hash: []byte{3, 4}, Metadata: []byte{9}},
  189. {Height: 1, Format: 3, Chunks: 7, Hash: []byte{1, 3}, Metadata: []byte{10}},
  190. {Height: 2, Format: 3, Chunks: 7, Hash: []byte{2, 3}, Metadata: []byte{11}},
  191. {Height: 3, Format: 3, Chunks: 7, Hash: []byte{3, 3}, Metadata: []byte{12}},
  192. },
  193. []*ssproto.SnapshotsResponse{
  194. {Height: 3, Format: 4, Chunks: 7, Hash: []byte{3, 4}, Metadata: []byte{9}},
  195. {Height: 3, Format: 3, Chunks: 7, Hash: []byte{3, 3}, Metadata: []byte{12}},
  196. {Height: 3, Format: 2, Chunks: 7, Hash: []byte{3, 2}, Metadata: []byte{3}},
  197. {Height: 3, Format: 1, Chunks: 7, Hash: []byte{3, 1}, Metadata: []byte{6}},
  198. {Height: 2, Format: 4, Chunks: 7, Hash: []byte{2, 4}, Metadata: []byte{8}},
  199. {Height: 2, Format: 3, Chunks: 7, Hash: []byte{2, 3}, Metadata: []byte{11}},
  200. {Height: 2, Format: 2, Chunks: 7, Hash: []byte{2, 2}, Metadata: []byte{2}},
  201. {Height: 2, Format: 1, Chunks: 7, Hash: []byte{2, 1}, Metadata: []byte{5}},
  202. {Height: 1, Format: 4, Chunks: 7, Hash: []byte{1, 4}, Metadata: []byte{7}},
  203. {Height: 1, Format: 3, Chunks: 7, Hash: []byte{1, 3}, Metadata: []byte{10}},
  204. },
  205. },
  206. }
  207. for name, tc := range testcases {
  208. tc := tc
  209. t.Run(name, func(t *testing.T) {
  210. // mock ABCI connection to return local snapshots
  211. conn := &proxymocks.AppConnSnapshot{}
  212. conn.On("ListSnapshotsSync", context.Background(), abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{
  213. Snapshots: tc.snapshots,
  214. }, nil)
  215. rts := setup(t, conn, nil, nil, 100)
  216. rts.snapshotInCh <- p2p.Envelope{
  217. From: p2p.PeerID{0xAA},
  218. Message: &ssproto.SnapshotsRequest{},
  219. }
  220. if len(tc.expectResponses) > 0 {
  221. retryUntil(t, func() bool { return len(rts.snapshotOutCh) == len(tc.expectResponses) }, time.Second)
  222. }
  223. responses := make([]*ssproto.SnapshotsResponse, len(tc.expectResponses))
  224. for i := 0; i < len(tc.expectResponses); i++ {
  225. e := <-rts.snapshotOutCh
  226. responses[i] = e.Message.(*ssproto.SnapshotsResponse)
  227. }
  228. require.Equal(t, tc.expectResponses, responses)
  229. require.Empty(t, rts.snapshotOutCh)
  230. })
  231. }
  232. }
  233. // retryUntil will continue to evaluate fn and will return successfully when true
  234. // or fail when the timeout is reached.
  235. func retryUntil(t *testing.T, fn func() bool, timeout time.Duration) {
  236. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  237. defer cancel()
  238. for {
  239. if fn() {
  240. return
  241. }
  242. require.NoError(t, ctx.Err())
  243. }
  244. }