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.

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