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.

295 lines
7.1 KiB

  1. package statesync
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "sync"
  7. "testing"
  8. "time"
  9. "github.com/fortytw2/leaktest"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "github.com/tendermint/tendermint/internal/p2p"
  13. "github.com/tendermint/tendermint/internal/test/factory"
  14. ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
  15. "github.com/tendermint/tendermint/types"
  16. )
  17. func TestDispatcherBasic(t *testing.T) {
  18. t.Cleanup(leaktest.Check(t))
  19. const numPeers = 5
  20. ch := make(chan p2p.Envelope, 100)
  21. closeCh := make(chan struct{})
  22. defer close(closeCh)
  23. d := NewDispatcher(ch)
  24. go handleRequests(t, d, ch, closeCh)
  25. peers := createPeerSet(numPeers)
  26. wg := sync.WaitGroup{}
  27. // make a bunch of async requests and require that the correct responses are
  28. // given
  29. for i := 0; i < numPeers; i++ {
  30. wg.Add(1)
  31. go func(height int64) {
  32. defer wg.Done()
  33. lb, err := d.LightBlock(context.Background(), height, peers[height-1])
  34. require.NoError(t, err)
  35. require.NotNil(t, lb)
  36. require.Equal(t, lb.Height, height)
  37. }(int64(i + 1))
  38. }
  39. wg.Wait()
  40. // assert that all calls were responded to
  41. assert.Empty(t, d.calls)
  42. }
  43. func TestDispatcherReturnsNoBlock(t *testing.T) {
  44. t.Cleanup(leaktest.Check(t))
  45. ch := make(chan p2p.Envelope, 100)
  46. d := NewDispatcher(ch)
  47. doneCh := make(chan struct{})
  48. peer := factory.NodeID("a")
  49. go func() {
  50. <-ch
  51. require.NoError(t, d.Respond(nil, peer))
  52. close(doneCh)
  53. }()
  54. lb, err := d.LightBlock(context.Background(), 1, peer)
  55. <-doneCh
  56. require.Nil(t, lb)
  57. require.Nil(t, err)
  58. }
  59. func TestDispatcherTimeOutWaitingOnLightBlock(t *testing.T) {
  60. t.Cleanup(leaktest.Check(t))
  61. ch := make(chan p2p.Envelope, 100)
  62. d := NewDispatcher(ch)
  63. peer := factory.NodeID("a")
  64. ctx, cancelFunc := context.WithTimeout(context.Background(), 10*time.Millisecond)
  65. defer cancelFunc()
  66. lb, err := d.LightBlock(ctx, 1, peer)
  67. require.Error(t, err)
  68. require.Equal(t, context.DeadlineExceeded, err)
  69. require.Nil(t, lb)
  70. }
  71. func TestDispatcherProviders(t *testing.T) {
  72. t.Cleanup(leaktest.Check(t))
  73. ch := make(chan p2p.Envelope, 100)
  74. chainID := "test-chain"
  75. closeCh := make(chan struct{})
  76. defer close(closeCh)
  77. d := NewDispatcher(ch)
  78. go handleRequests(t, d, ch, closeCh)
  79. peers := createPeerSet(5)
  80. providers := make([]*BlockProvider, len(peers))
  81. for idx, peer := range peers {
  82. providers[idx] = NewBlockProvider(peer, chainID, d)
  83. }
  84. require.Len(t, providers, 5)
  85. for i, p := range providers {
  86. assert.Equal(t, string(peers[i]), p.String(), i)
  87. lb, err := p.LightBlock(context.Background(), 10)
  88. assert.NoError(t, err)
  89. assert.NotNil(t, lb)
  90. }
  91. }
  92. func TestPeerListBasic(t *testing.T) {
  93. t.Cleanup(leaktest.Check(t))
  94. ctx, cancel := context.WithCancel(context.Background())
  95. defer cancel()
  96. peerList := newPeerList()
  97. assert.Zero(t, peerList.Len())
  98. numPeers := 10
  99. peerSet := createPeerSet(numPeers)
  100. for _, peer := range peerSet {
  101. peerList.Append(peer)
  102. }
  103. for idx, peer := range peerList.All() {
  104. assert.Equal(t, peer, peerSet[idx])
  105. }
  106. assert.Equal(t, numPeers, peerList.Len())
  107. half := numPeers / 2
  108. for i := 0; i < half; i++ {
  109. assert.Equal(t, peerSet[i], peerList.Pop(ctx))
  110. }
  111. assert.Equal(t, half, peerList.Len())
  112. // removing a peer that doesn't exist should not change the list
  113. peerList.Remove(types.NodeID("lp"))
  114. assert.Equal(t, half, peerList.Len())
  115. // removing a peer that exists should decrease the list size by one
  116. peerList.Remove(peerSet[half])
  117. assert.Equal(t, numPeers-half-1, peerList.Len())
  118. // popping the next peer should work as expected
  119. assert.Equal(t, peerSet[half+1], peerList.Pop(ctx))
  120. assert.Equal(t, numPeers-half-2, peerList.Len())
  121. // append the two peers back
  122. peerList.Append(peerSet[half])
  123. peerList.Append(peerSet[half+1])
  124. assert.Equal(t, half, peerList.Len())
  125. }
  126. func TestPeerListBlocksWhenEmpty(t *testing.T) {
  127. t.Cleanup(leaktest.Check(t))
  128. peerList := newPeerList()
  129. require.Zero(t, peerList.Len())
  130. doneCh := make(chan struct{})
  131. ctx, cancel := context.WithCancel(context.Background())
  132. defer cancel()
  133. go func() {
  134. peerList.Pop(ctx)
  135. close(doneCh)
  136. }()
  137. select {
  138. case <-doneCh:
  139. t.Error("empty peer list should not have returned result")
  140. case <-time.After(100 * time.Millisecond):
  141. }
  142. }
  143. func TestEmptyPeerListReturnsWhenContextCanceled(t *testing.T) {
  144. t.Cleanup(leaktest.Check(t))
  145. peerList := newPeerList()
  146. require.Zero(t, peerList.Len())
  147. doneCh := make(chan struct{})
  148. ctx := context.Background()
  149. wrapped, cancel := context.WithCancel(ctx)
  150. go func() {
  151. peerList.Pop(wrapped)
  152. close(doneCh)
  153. }()
  154. select {
  155. case <-doneCh:
  156. t.Error("empty peer list should not have returned result")
  157. case <-time.After(100 * time.Millisecond):
  158. }
  159. cancel()
  160. select {
  161. case <-doneCh:
  162. case <-time.After(100 * time.Millisecond):
  163. t.Error("peer list should have returned after context canceled")
  164. }
  165. }
  166. func TestPeerListConcurrent(t *testing.T) {
  167. t.Cleanup(leaktest.Check(t))
  168. ctx, cancel := context.WithCancel(context.Background())
  169. defer cancel()
  170. peerList := newPeerList()
  171. numPeers := 10
  172. wg := sync.WaitGroup{}
  173. // we run a set of goroutines requesting the next peer in the list. As the
  174. // peer list hasn't been populated each these go routines should block
  175. for i := 0; i < numPeers/2; i++ {
  176. go func() {
  177. _ = peerList.Pop(ctx)
  178. wg.Done()
  179. }()
  180. }
  181. // now we add the peers to the list, this should allow the previously
  182. // blocked go routines to unblock
  183. for _, peer := range createPeerSet(numPeers) {
  184. wg.Add(1)
  185. peerList.Append(peer)
  186. }
  187. // we request the second half of the peer set
  188. for i := 0; i < numPeers/2; i++ {
  189. go func() {
  190. _ = peerList.Pop(ctx)
  191. wg.Done()
  192. }()
  193. }
  194. // we use a context with cancel and a separate go routine to wait for all
  195. // the other goroutines to close.
  196. go func() { wg.Wait(); cancel() }()
  197. select {
  198. case <-time.After(time.Second):
  199. // not all of the blocked go routines waiting on peers have closed after
  200. // one second. This likely means the list got blocked.
  201. t.Failed()
  202. case <-ctx.Done():
  203. // there should be no peers remaining
  204. require.Equal(t, 0, peerList.Len())
  205. }
  206. }
  207. func TestPeerListRemove(t *testing.T) {
  208. peerList := newPeerList()
  209. numPeers := 10
  210. peerSet := createPeerSet(numPeers)
  211. for _, peer := range peerSet {
  212. peerList.Append(peer)
  213. }
  214. for _, peer := range peerSet {
  215. peerList.Remove(peer)
  216. for _, p := range peerList.All() {
  217. require.NotEqual(t, p, peer)
  218. }
  219. numPeers--
  220. require.Equal(t, numPeers, peerList.Len())
  221. }
  222. }
  223. // handleRequests is a helper function usually run in a separate go routine to
  224. // imitate the expected responses of the reactor wired to the dispatcher
  225. func handleRequests(t *testing.T, d *Dispatcher, ch chan p2p.Envelope, closeCh chan struct{}) {
  226. t.Helper()
  227. for {
  228. select {
  229. case request := <-ch:
  230. height := request.Message.(*ssproto.LightBlockRequest).Height
  231. peer := request.To
  232. resp := mockLBResp(t, peer, int64(height), time.Now())
  233. block, _ := resp.block.ToProto()
  234. require.NoError(t, d.Respond(block, resp.peer))
  235. case <-closeCh:
  236. return
  237. }
  238. }
  239. }
  240. func createPeerSet(num int) []types.NodeID {
  241. peers := make([]types.NodeID, num)
  242. for i := 0; i < num; i++ {
  243. peers[i], _ = types.NewNodeID(strings.Repeat(fmt.Sprintf("%d", i), 2*types.NodeIDByteLength))
  244. }
  245. return peers
  246. }