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.

179 lines
4.2 KiB

  1. package statesync
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "sync"
  7. "testing"
  8. "time"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. "github.com/tendermint/tendermint/internal/p2p"
  12. ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
  13. )
  14. func TestDispatcherBasic(t *testing.T) {
  15. ch := make(chan p2p.Envelope, 100)
  16. closeCh := make(chan struct{})
  17. defer close(closeCh)
  18. d := newDispatcher(ch, 1*time.Second)
  19. go handleRequests(t, d, ch, closeCh)
  20. peers := createPeerSet(5)
  21. for _, peer := range peers {
  22. d.addPeer(peer)
  23. }
  24. wg := sync.WaitGroup{}
  25. // make a bunch of async requests and require that the correct responses are
  26. // given
  27. for i := 1; i < 10; i++ {
  28. wg.Add(1)
  29. go func(height int64) {
  30. defer wg.Done()
  31. lb, peer, err := d.LightBlock(context.Background(), height)
  32. require.NoError(t, err)
  33. require.NotNil(t, lb)
  34. require.Equal(t, lb.Height, height)
  35. require.Contains(t, peers, peer)
  36. }(int64(i))
  37. }
  38. wg.Wait()
  39. }
  40. func TestDispatcherProviders(t *testing.T) {
  41. ch := make(chan p2p.Envelope, 100)
  42. chainID := "state-sync-test"
  43. closeCh := make(chan struct{})
  44. defer close(closeCh)
  45. d := newDispatcher(ch, 1*time.Second)
  46. go handleRequests(t, d, ch, closeCh)
  47. peers := createPeerSet(5)
  48. for _, peer := range peers {
  49. d.addPeer(peer)
  50. }
  51. providers := d.Providers(chainID, 5*time.Second)
  52. require.Len(t, providers, 5)
  53. for i, p := range providers {
  54. bp, ok := p.(*blockProvider)
  55. require.True(t, ok)
  56. assert.Equal(t, bp.String(), string(peers[i]))
  57. lb, err := p.LightBlock(context.Background(), 10)
  58. assert.Error(t, err)
  59. assert.Nil(t, lb)
  60. }
  61. }
  62. func TestPeerListBasic(t *testing.T) {
  63. peerList := newPeerList()
  64. assert.Zero(t, peerList.Len())
  65. numPeers := 10
  66. peerSet := createPeerSet(numPeers)
  67. for _, peer := range peerSet {
  68. peerList.Append(peer)
  69. }
  70. for idx, peer := range peerList.Peers() {
  71. assert.Equal(t, peer, peerSet[idx])
  72. }
  73. assert.Equal(t, numPeers, peerList.Len())
  74. half := numPeers / 2
  75. for i := 0; i < half; i++ {
  76. assert.Equal(t, peerSet[i], peerList.Pop())
  77. }
  78. assert.Equal(t, half, peerList.Len())
  79. peerList.Remove(p2p.NodeID("lp"))
  80. assert.Equal(t, half, peerList.Len())
  81. peerList.Remove(peerSet[half])
  82. half++
  83. assert.Equal(t, peerSet[half], peerList.Pop())
  84. }
  85. func TestPeerListConcurrent(t *testing.T) {
  86. peerList := newPeerList()
  87. numPeers := 10
  88. wg := sync.WaitGroup{}
  89. // we run a set of goroutines requesting the next peer in the list. As the
  90. // peer list hasn't been populated each these go routines should block
  91. for i := 0; i < numPeers/2; i++ {
  92. go func() {
  93. _ = peerList.Pop()
  94. wg.Done()
  95. }()
  96. }
  97. // now we add the peers to the list, this should allow the previously
  98. // blocked go routines to unblock
  99. for _, peer := range createPeerSet(numPeers) {
  100. wg.Add(1)
  101. peerList.Append(peer)
  102. }
  103. // we request the second half of the peer set
  104. for i := 0; i < numPeers/2; i++ {
  105. go func() {
  106. _ = peerList.Pop()
  107. wg.Done()
  108. }()
  109. }
  110. // we use a context with cancel and a separate go routine to wait for all
  111. // the other goroutines to close.
  112. ctx, cancel := context.WithCancel(context.Background())
  113. go func() { wg.Wait(); cancel() }()
  114. select {
  115. case <-time.After(time.Second):
  116. // not all of the blocked go routines waiting on peers have closed after
  117. // one second. This likely means the list got blocked.
  118. t.Failed()
  119. case <-ctx.Done():
  120. // there should be no peers remaining
  121. require.Equal(t, 0, peerList.Len())
  122. }
  123. }
  124. // handleRequests is a helper function usually run in a separate go routine to
  125. // imitate the expected responses of the reactor wired to the dispatcher
  126. func handleRequests(t *testing.T, d *dispatcher, ch chan p2p.Envelope, closeCh chan struct{}) {
  127. t.Helper()
  128. for {
  129. select {
  130. case request := <-ch:
  131. height := request.Message.(*ssproto.LightBlockRequest).Height
  132. peer := request.To
  133. resp := mockLBResp(t, peer, int64(height), time.Now())
  134. block, _ := resp.block.ToProto()
  135. require.NoError(t, d.respond(block, resp.peer))
  136. case <-closeCh:
  137. return
  138. }
  139. }
  140. }
  141. func createPeerSet(num int) []p2p.NodeID {
  142. peers := make([]p2p.NodeID, num)
  143. for i := 0; i < num; i++ {
  144. peers[i], _ = p2p.NewNodeID(strings.Repeat(fmt.Sprintf("%d", i), 2*p2p.NodeIDByteLength))
  145. }
  146. return peers
  147. }