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.

325 lines
9.6 KiB

  1. package statesync
  2. import (
  3. "testing"
  4. "github.com/stretchr/testify/assert"
  5. "github.com/stretchr/testify/mock"
  6. "github.com/stretchr/testify/require"
  7. "github.com/tendermint/tendermint/p2p"
  8. p2pmocks "github.com/tendermint/tendermint/p2p/mocks"
  9. "github.com/tendermint/tendermint/statesync/mocks"
  10. )
  11. func TestSnapshot_Key(t *testing.T) {
  12. testcases := map[string]struct {
  13. modify func(*snapshot)
  14. }{
  15. "new height": {func(s *snapshot) { s.Height = 9 }},
  16. "new format": {func(s *snapshot) { s.Format = 9 }},
  17. "new chunk count": {func(s *snapshot) { s.Chunks = 9 }},
  18. "new hash": {func(s *snapshot) { s.Hash = []byte{9} }},
  19. "no metadata": {func(s *snapshot) { s.Metadata = nil }},
  20. }
  21. for name, tc := range testcases {
  22. tc := tc
  23. t.Run(name, func(t *testing.T) {
  24. s := snapshot{
  25. Height: 3,
  26. Format: 1,
  27. Chunks: 7,
  28. Hash: []byte{1, 2, 3},
  29. Metadata: []byte{255},
  30. }
  31. before := s.Key()
  32. tc.modify(&s)
  33. after := s.Key()
  34. assert.NotEqual(t, before, after)
  35. })
  36. }
  37. }
  38. func TestSnapshotPool_Add(t *testing.T) {
  39. stateProvider := &mocks.StateProvider{}
  40. stateProvider.On("AppHash", mock.Anything, uint64(1)).Return([]byte("app_hash"), nil)
  41. peer := &p2pmocks.Peer{}
  42. peer.On("ID").Return(p2p.ID("id"))
  43. // Adding to the pool should work
  44. pool := newSnapshotPool(stateProvider)
  45. added, err := pool.Add(peer, &snapshot{
  46. Height: 1,
  47. Format: 1,
  48. Chunks: 1,
  49. Hash: []byte{1},
  50. })
  51. require.NoError(t, err)
  52. assert.True(t, added)
  53. // Adding again from a different peer should return false
  54. otherPeer := &p2pmocks.Peer{}
  55. otherPeer.On("ID").Return(p2p.ID("other"))
  56. added, err = pool.Add(peer, &snapshot{
  57. Height: 1,
  58. Format: 1,
  59. Chunks: 1,
  60. Hash: []byte{1},
  61. })
  62. require.NoError(t, err)
  63. assert.False(t, added)
  64. // The pool should have populated the snapshot with the trusted app hash
  65. snapshot := pool.Best()
  66. require.NotNil(t, snapshot)
  67. assert.Equal(t, []byte("app_hash"), snapshot.trustedAppHash)
  68. stateProvider.AssertExpectations(t)
  69. }
  70. func TestSnapshotPool_GetPeer(t *testing.T) {
  71. stateProvider := &mocks.StateProvider{}
  72. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  73. pool := newSnapshotPool(stateProvider)
  74. s := &snapshot{Height: 1, Format: 1, Chunks: 1, Hash: []byte{1}}
  75. peerA := &p2pmocks.Peer{}
  76. peerA.On("ID").Return(p2p.ID("a"))
  77. peerB := &p2pmocks.Peer{}
  78. peerB.On("ID").Return(p2p.ID("b"))
  79. _, err := pool.Add(peerA, s)
  80. require.NoError(t, err)
  81. _, err = pool.Add(peerB, s)
  82. require.NoError(t, err)
  83. _, err = pool.Add(peerA, &snapshot{Height: 2, Format: 1, Chunks: 1, Hash: []byte{1}})
  84. require.NoError(t, err)
  85. // GetPeer currently picks a random peer, so lets run it until we've seen both.
  86. seenA := false
  87. seenB := false
  88. for !seenA || !seenB {
  89. peer := pool.GetPeer(s)
  90. switch peer.ID() {
  91. case p2p.ID("a"):
  92. seenA = true
  93. case p2p.ID("b"):
  94. seenB = true
  95. }
  96. }
  97. // GetPeer should return nil for an unknown snapshot
  98. peer := pool.GetPeer(&snapshot{Height: 9, Format: 9})
  99. assert.Nil(t, peer)
  100. }
  101. func TestSnapshotPool_GetPeers(t *testing.T) {
  102. stateProvider := &mocks.StateProvider{}
  103. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  104. pool := newSnapshotPool(stateProvider)
  105. s := &snapshot{Height: 1, Format: 1, Chunks: 1, Hash: []byte{1}}
  106. peerA := &p2pmocks.Peer{}
  107. peerA.On("ID").Return(p2p.ID("a"))
  108. peerB := &p2pmocks.Peer{}
  109. peerB.On("ID").Return(p2p.ID("b"))
  110. _, err := pool.Add(peerA, s)
  111. require.NoError(t, err)
  112. _, err = pool.Add(peerB, s)
  113. require.NoError(t, err)
  114. _, err = pool.Add(peerA, &snapshot{Height: 2, Format: 1, Chunks: 1, Hash: []byte{2}})
  115. require.NoError(t, err)
  116. peers := pool.GetPeers(s)
  117. assert.Len(t, peers, 2)
  118. assert.EqualValues(t, "a", peers[0].ID())
  119. assert.EqualValues(t, "b", peers[1].ID())
  120. }
  121. func TestSnapshotPool_Ranked_Best(t *testing.T) {
  122. stateProvider := &mocks.StateProvider{}
  123. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  124. pool := newSnapshotPool(stateProvider)
  125. // snapshots in expected order (best to worst). Highest height wins, then highest format.
  126. // Snapshots with different chunk hashes are considered different, and the most peers is
  127. // tie-breaker.
  128. expectSnapshots := []struct {
  129. snapshot *snapshot
  130. peers []string
  131. }{
  132. {&snapshot{Height: 2, Format: 2, Chunks: 4, Hash: []byte{1, 3}}, []string{"a", "b", "c"}},
  133. {&snapshot{Height: 2, Format: 2, Chunks: 5, Hash: []byte{1, 2}}, []string{"a"}},
  134. {&snapshot{Height: 2, Format: 1, Chunks: 3, Hash: []byte{1, 2}}, []string{"a", "b"}},
  135. {&snapshot{Height: 1, Format: 2, Chunks: 5, Hash: []byte{1, 2}}, []string{"a", "b"}},
  136. {&snapshot{Height: 1, Format: 1, Chunks: 4, Hash: []byte{1, 2}}, []string{"a", "b", "c"}},
  137. }
  138. // Add snapshots in reverse order, to make sure the pool enforces some order.
  139. for i := len(expectSnapshots) - 1; i >= 0; i-- {
  140. for _, peerID := range expectSnapshots[i].peers {
  141. peer := &p2pmocks.Peer{}
  142. peer.On("ID").Return(p2p.ID(peerID))
  143. _, err := pool.Add(peer, expectSnapshots[i].snapshot)
  144. require.NoError(t, err)
  145. }
  146. }
  147. // Ranked should return the snapshots in the same order
  148. ranked := pool.Ranked()
  149. assert.Len(t, ranked, len(expectSnapshots))
  150. for i := range ranked {
  151. assert.Equal(t, expectSnapshots[i].snapshot, ranked[i])
  152. }
  153. // Check that best snapshots are returned in expected order
  154. for i := range expectSnapshots {
  155. snapshot := expectSnapshots[i].snapshot
  156. require.Equal(t, snapshot, pool.Best())
  157. pool.Reject(snapshot)
  158. }
  159. assert.Nil(t, pool.Best())
  160. }
  161. func TestSnapshotPool_Reject(t *testing.T) {
  162. stateProvider := &mocks.StateProvider{}
  163. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  164. pool := newSnapshotPool(stateProvider)
  165. peer := &p2pmocks.Peer{}
  166. peer.On("ID").Return(p2p.ID("id"))
  167. snapshots := []*snapshot{
  168. {Height: 2, Format: 2, Chunks: 1, Hash: []byte{1, 2}},
  169. {Height: 2, Format: 1, Chunks: 1, Hash: []byte{1, 2}},
  170. {Height: 1, Format: 2, Chunks: 1, Hash: []byte{1, 2}},
  171. {Height: 1, Format: 1, Chunks: 1, Hash: []byte{1, 2}},
  172. }
  173. for _, s := range snapshots {
  174. _, err := pool.Add(peer, s)
  175. require.NoError(t, err)
  176. }
  177. pool.Reject(snapshots[0])
  178. assert.Equal(t, snapshots[1:], pool.Ranked())
  179. added, err := pool.Add(peer, snapshots[0])
  180. require.NoError(t, err)
  181. assert.False(t, added)
  182. added, err = pool.Add(peer, &snapshot{Height: 3, Format: 3, Chunks: 1, Hash: []byte{1}})
  183. require.NoError(t, err)
  184. assert.True(t, added)
  185. }
  186. func TestSnapshotPool_RejectFormat(t *testing.T) {
  187. stateProvider := &mocks.StateProvider{}
  188. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  189. pool := newSnapshotPool(stateProvider)
  190. peer := &p2pmocks.Peer{}
  191. peer.On("ID").Return(p2p.ID("id"))
  192. snapshots := []*snapshot{
  193. {Height: 2, Format: 2, Chunks: 1, Hash: []byte{1, 2}},
  194. {Height: 2, Format: 1, Chunks: 1, Hash: []byte{1, 2}},
  195. {Height: 1, Format: 2, Chunks: 1, Hash: []byte{1, 2}},
  196. {Height: 1, Format: 1, Chunks: 1, Hash: []byte{1, 2}},
  197. }
  198. for _, s := range snapshots {
  199. _, err := pool.Add(peer, s)
  200. require.NoError(t, err)
  201. }
  202. pool.RejectFormat(1)
  203. assert.Equal(t, []*snapshot{snapshots[0], snapshots[2]}, pool.Ranked())
  204. added, err := pool.Add(peer, &snapshot{Height: 3, Format: 1, Chunks: 1, Hash: []byte{1}})
  205. require.NoError(t, err)
  206. assert.False(t, added)
  207. assert.Equal(t, []*snapshot{snapshots[0], snapshots[2]}, pool.Ranked())
  208. added, err = pool.Add(peer, &snapshot{Height: 3, Format: 3, Chunks: 1, Hash: []byte{1}})
  209. require.NoError(t, err)
  210. assert.True(t, added)
  211. }
  212. func TestSnapshotPool_RejectPeer(t *testing.T) {
  213. stateProvider := &mocks.StateProvider{}
  214. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  215. pool := newSnapshotPool(stateProvider)
  216. peerA := &p2pmocks.Peer{}
  217. peerA.On("ID").Return(p2p.ID("a"))
  218. peerB := &p2pmocks.Peer{}
  219. peerB.On("ID").Return(p2p.ID("b"))
  220. s1 := &snapshot{Height: 1, Format: 1, Chunks: 1, Hash: []byte{1}}
  221. s2 := &snapshot{Height: 2, Format: 1, Chunks: 1, Hash: []byte{2}}
  222. s3 := &snapshot{Height: 3, Format: 1, Chunks: 1, Hash: []byte{2}}
  223. _, err := pool.Add(peerA, s1)
  224. require.NoError(t, err)
  225. _, err = pool.Add(peerA, s2)
  226. require.NoError(t, err)
  227. _, err = pool.Add(peerB, s2)
  228. require.NoError(t, err)
  229. _, err = pool.Add(peerB, s3)
  230. require.NoError(t, err)
  231. pool.RejectPeer(peerA.ID())
  232. assert.Empty(t, pool.GetPeers(s1))
  233. peers2 := pool.GetPeers(s2)
  234. assert.Len(t, peers2, 1)
  235. assert.EqualValues(t, "b", peers2[0].ID())
  236. peers3 := pool.GetPeers(s2)
  237. assert.Len(t, peers3, 1)
  238. assert.EqualValues(t, "b", peers3[0].ID())
  239. // it should no longer be possible to add the peer back
  240. _, err = pool.Add(peerA, s1)
  241. require.NoError(t, err)
  242. assert.Empty(t, pool.GetPeers(s1))
  243. }
  244. func TestSnapshotPool_RemovePeer(t *testing.T) {
  245. stateProvider := &mocks.StateProvider{}
  246. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  247. pool := newSnapshotPool(stateProvider)
  248. peerA := &p2pmocks.Peer{}
  249. peerA.On("ID").Return(p2p.ID("a"))
  250. peerB := &p2pmocks.Peer{}
  251. peerB.On("ID").Return(p2p.ID("b"))
  252. s1 := &snapshot{Height: 1, Format: 1, Chunks: 1, Hash: []byte{1}}
  253. s2 := &snapshot{Height: 2, Format: 1, Chunks: 1, Hash: []byte{2}}
  254. _, err := pool.Add(peerA, s1)
  255. require.NoError(t, err)
  256. _, err = pool.Add(peerA, s2)
  257. require.NoError(t, err)
  258. _, err = pool.Add(peerB, s1)
  259. require.NoError(t, err)
  260. pool.RemovePeer(peerA.ID())
  261. peers1 := pool.GetPeers(s1)
  262. assert.Len(t, peers1, 1)
  263. assert.EqualValues(t, "b", peers1[0].ID())
  264. peers2 := pool.GetPeers(s2)
  265. assert.Empty(t, peers2)
  266. // it should still be possible to add the peer back
  267. _, err = pool.Add(peerA, s1)
  268. require.NoError(t, err)
  269. peers1 = pool.GetPeers(s1)
  270. assert.Len(t, peers1, 2)
  271. assert.EqualValues(t, "a", peers1[0].ID())
  272. assert.EqualValues(t, "b", peers1[1].ID())
  273. }