|
|
- package statesync
-
- import (
- "context"
- "fmt"
- "strings"
- "sync"
- "testing"
- "time"
-
- "github.com/fortytw2/leaktest"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/tendermint/tendermint/internal/p2p"
- ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
- "github.com/tendermint/tendermint/types"
- )
-
- func TestDispatcherBasic(t *testing.T) {
- t.Cleanup(leaktest.Check(t))
-
- ch := make(chan p2p.Envelope, 100)
- closeCh := make(chan struct{})
- defer close(closeCh)
-
- d := newDispatcher(ch, 1*time.Second)
-
- go handleRequests(t, d, ch, closeCh)
-
- peers := createPeerSet(5)
- for _, peer := range peers {
- d.addPeer(peer)
- }
-
- wg := sync.WaitGroup{}
-
- // make a bunch of async requests and require that the correct responses are
- // given
- for i := 1; i < 10; i++ {
- wg.Add(1)
- go func(height int64) {
- defer wg.Done()
- lb, peer, err := d.LightBlock(context.Background(), height)
- require.NoError(t, err)
- require.NotNil(t, lb)
- require.Equal(t, lb.Height, height)
- require.Contains(t, peers, peer)
- }(int64(i))
- }
- wg.Wait()
- }
-
- func TestDispatcherReturnsNoBlock(t *testing.T) {
- t.Cleanup(leaktest.Check(t))
- ch := make(chan p2p.Envelope, 100)
- d := newDispatcher(ch, 1*time.Second)
- peerFromSet := createPeerSet(1)[0]
- d.addPeer(peerFromSet)
- doneCh := make(chan struct{})
-
- go func() {
- <-ch
- require.NoError(t, d.respond(nil, peerFromSet))
- close(doneCh)
- }()
-
- lb, peerResult, err := d.LightBlock(context.Background(), 1)
- <-doneCh
-
- require.Nil(t, lb)
- require.Nil(t, err)
- require.Equal(t, peerFromSet, peerResult)
- }
-
- func TestDispatcherErrorsWhenNoPeers(t *testing.T) {
- t.Cleanup(leaktest.Check(t))
- ch := make(chan p2p.Envelope, 100)
- d := newDispatcher(ch, 1*time.Second)
-
- lb, peerResult, err := d.LightBlock(context.Background(), 1)
-
- require.Nil(t, lb)
- require.Empty(t, peerResult)
- require.Equal(t, errNoConnectedPeers, err)
- }
-
- func TestDispatcherReturnsBlockOncePeerAvailable(t *testing.T) {
- t.Cleanup(leaktest.Check(t))
- dispatcherRequestCh := make(chan p2p.Envelope, 100)
- d := newDispatcher(dispatcherRequestCh, 1*time.Second)
- peerFromSet := createPeerSet(1)[0]
- d.addPeer(peerFromSet)
- ctx := context.Background()
- wrapped, cancelFunc := context.WithCancel(ctx)
-
- doneCh := make(chan struct{})
- go func() {
- lb, peerResult, err := d.LightBlock(wrapped, 1)
- require.Nil(t, lb)
- require.Equal(t, peerFromSet, peerResult)
- require.Nil(t, err)
-
- // calls to dispatcher.Lightblock write into the dispatcher's requestCh.
- // we read from the requestCh here to unblock the requestCh for future
- // calls.
- <-dispatcherRequestCh
- close(doneCh)
- }()
- cancelFunc()
- <-doneCh
-
- go func() {
- <-dispatcherRequestCh
- lb := &types.LightBlock{}
- asProto, err := lb.ToProto()
- require.Nil(t, err)
- err = d.respond(asProto, peerFromSet)
- require.Nil(t, err)
- }()
-
- lb, peerResult, err := d.LightBlock(context.Background(), 1)
-
- require.NotNil(t, lb)
- require.Equal(t, peerFromSet, peerResult)
- require.Nil(t, err)
- }
-
- func TestDispatcherProviders(t *testing.T) {
- t.Cleanup(leaktest.Check(t))
-
- ch := make(chan p2p.Envelope, 100)
- chainID := "state-sync-test"
- closeCh := make(chan struct{})
- defer close(closeCh)
-
- d := newDispatcher(ch, 1*time.Second)
-
- go handleRequests(t, d, ch, closeCh)
-
- peers := createPeerSet(5)
- for _, peer := range peers {
- d.addPeer(peer)
- }
-
- providers := d.Providers(chainID, 5*time.Second)
- require.Len(t, providers, 5)
- for i, p := range providers {
- bp, ok := p.(*blockProvider)
- require.True(t, ok)
- assert.Equal(t, bp.String(), string(peers[i]))
- lb, err := p.LightBlock(context.Background(), 10)
- assert.Error(t, err)
- assert.Nil(t, lb)
- }
- }
-
- func TestPeerListBasic(t *testing.T) {
- t.Cleanup(leaktest.Check(t))
- peerList := newPeerList()
- assert.Zero(t, peerList.Len())
- numPeers := 10
- peerSet := createPeerSet(numPeers)
-
- for _, peer := range peerSet {
- peerList.Append(peer)
- }
-
- for idx, peer := range peerList.Peers() {
- assert.Equal(t, peer, peerSet[idx])
- }
-
- assert.Equal(t, numPeers, peerList.Len())
-
- half := numPeers / 2
- for i := 0; i < half; i++ {
- assert.Equal(t, peerSet[i], peerList.Pop(ctx))
- }
- assert.Equal(t, half, peerList.Len())
-
- peerList.Remove(types.NodeID("lp"))
- assert.Equal(t, half, peerList.Len())
-
- peerList.Remove(peerSet[half])
- half++
- assert.Equal(t, peerSet[half], peerList.Pop(ctx))
-
- }
-
- func TestPeerListBlocksWhenEmpty(t *testing.T) {
- t.Cleanup(leaktest.Check(t))
- peerList := newPeerList()
- require.Zero(t, peerList.Len())
- doneCh := make(chan struct{})
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
- go func() {
- peerList.Pop(ctx)
- close(doneCh)
- }()
- select {
- case <-doneCh:
- t.Error("empty peer list should not have returned result")
- case <-time.After(100 * time.Millisecond):
- }
- }
-
- func TestEmptyPeerListReturnsWhenContextCanceled(t *testing.T) {
- t.Cleanup(leaktest.Check(t))
- peerList := newPeerList()
- require.Zero(t, peerList.Len())
- doneCh := make(chan struct{})
- ctx := context.Background()
- wrapped, cancel := context.WithCancel(ctx)
- go func() {
- peerList.Pop(wrapped)
- close(doneCh)
- }()
- select {
- case <-doneCh:
- t.Error("empty peer list should not have returned result")
- case <-time.After(100 * time.Millisecond):
- }
-
- cancel()
-
- select {
- case <-doneCh:
- case <-time.After(100 * time.Millisecond):
- t.Error("peer list should have returned after context canceled")
- }
- }
-
- func TestPeerListConcurrent(t *testing.T) {
- t.Cleanup(leaktest.Check(t))
- peerList := newPeerList()
- numPeers := 10
-
- wg := sync.WaitGroup{}
- // we run a set of goroutines requesting the next peer in the list. As the
- // peer list hasn't been populated each these go routines should block
- for i := 0; i < numPeers/2; i++ {
- go func() {
- _ = peerList.Pop(ctx)
- wg.Done()
- }()
- }
-
- // now we add the peers to the list, this should allow the previously
- // blocked go routines to unblock
- for _, peer := range createPeerSet(numPeers) {
- wg.Add(1)
- peerList.Append(peer)
- }
-
- // we request the second half of the peer set
- for i := 0; i < numPeers/2; i++ {
- go func() {
- _ = peerList.Pop(ctx)
- wg.Done()
- }()
- }
-
- // we use a context with cancel and a separate go routine to wait for all
- // the other goroutines to close.
- ctx, cancel := context.WithCancel(context.Background())
- go func() { wg.Wait(); cancel() }()
-
- select {
- case <-time.After(time.Second):
- // not all of the blocked go routines waiting on peers have closed after
- // one second. This likely means the list got blocked.
- t.Failed()
- case <-ctx.Done():
- // there should be no peers remaining
- require.Equal(t, 0, peerList.Len())
- }
- }
-
- // handleRequests is a helper function usually run in a separate go routine to
- // imitate the expected responses of the reactor wired to the dispatcher
- func handleRequests(t *testing.T, d *dispatcher, ch chan p2p.Envelope, closeCh chan struct{}) {
- t.Helper()
- for {
- select {
- case request := <-ch:
- height := request.Message.(*ssproto.LightBlockRequest).Height
- peer := request.To
- resp := mockLBResp(t, peer, int64(height), time.Now())
- block, _ := resp.block.ToProto()
- require.NoError(t, d.respond(block, resp.peer))
- case <-closeCh:
- return
- }
- }
- }
-
- func createPeerSet(num int) []types.NodeID {
- peers := make([]types.NodeID, num)
- for i := 0; i < num; i++ {
- peers[i], _ = types.NewNodeID(strings.Repeat(fmt.Sprintf("%d", i), 2*types.NodeIDByteLength))
- }
- return peers
- }
|