github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/blockchain/v0/pool_test.go (about)

     1  package v0
     2  
     3  import (
     4  	"fmt"
     5  	mrand "math/rand"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/lazyledger/lazyledger-core/libs/log"
    13  	tmrand "github.com/lazyledger/lazyledger-core/libs/rand"
    14  	"github.com/lazyledger/lazyledger-core/p2p"
    15  	"github.com/lazyledger/lazyledger-core/types"
    16  )
    17  
    18  func init() {
    19  	peerTimeout = 2 * time.Second
    20  }
    21  
    22  type testPeer struct {
    23  	id        p2p.ID
    24  	base      int64
    25  	height    int64
    26  	inputChan chan inputData // make sure each peer's data is sequential
    27  }
    28  
    29  type inputData struct {
    30  	t       *testing.T
    31  	pool    *BlockPool
    32  	request BlockRequest
    33  }
    34  
    35  func (p testPeer) runInputRoutine() {
    36  	go func() {
    37  		for input := range p.inputChan {
    38  			p.simulateInput(input)
    39  		}
    40  	}()
    41  }
    42  
    43  // Request desired, pretend like we got the block immediately.
    44  func (p testPeer) simulateInput(input inputData) {
    45  	block := &types.Block{Header: types.Header{Height: input.request.Height}}
    46  	input.pool.AddBlock(input.request.PeerID, block, 123)
    47  	// TODO: uncommenting this creates a race which is detected by:
    48  	// https://github.com/golang/go/blob/2bd767b1022dd3254bcec469f0ee164024726486/src/testing/testing.go#L854-L856
    49  	// see: https://github.com/tendermint/tendermint/issues/3390#issue-418379890
    50  	// input.t.Logf("Added block from peer %v (height: %v)", input.request.PeerID, input.request.Height)
    51  }
    52  
    53  type testPeers map[p2p.ID]testPeer
    54  
    55  func (ps testPeers) start() {
    56  	for _, v := range ps {
    57  		v.runInputRoutine()
    58  	}
    59  }
    60  
    61  func (ps testPeers) stop() {
    62  	for _, v := range ps {
    63  		close(v.inputChan)
    64  	}
    65  }
    66  
    67  func makePeers(numPeers int, minHeight, maxHeight int64) testPeers {
    68  	peers := make(testPeers, numPeers)
    69  	for i := 0; i < numPeers; i++ {
    70  		peerID := p2p.ID(tmrand.Str(12))
    71  		height := minHeight + mrand.Int63n(maxHeight-minHeight)
    72  		base := minHeight + int64(i)
    73  		if base > height {
    74  			base = height
    75  		}
    76  		peers[peerID] = testPeer{peerID, base, height, make(chan inputData, 10)}
    77  	}
    78  	return peers
    79  }
    80  
    81  func TestBlockPoolBasic(t *testing.T) {
    82  	start := int64(42)
    83  	peers := makePeers(10, start+1, 1000)
    84  	errorsCh := make(chan peerError, 1000)
    85  	requestsCh := make(chan BlockRequest, 1000)
    86  	pool := NewBlockPool(start, requestsCh, errorsCh)
    87  	pool.SetLogger(log.TestingLogger())
    88  
    89  	err := pool.Start()
    90  	if err != nil {
    91  		t.Error(err)
    92  	}
    93  
    94  	t.Cleanup(func() {
    95  		if err := pool.Stop(); err != nil {
    96  			t.Error(err)
    97  		}
    98  	})
    99  
   100  	peers.start()
   101  	defer peers.stop()
   102  
   103  	// Introduce each peer.
   104  	go func() {
   105  		for _, peer := range peers {
   106  			pool.SetPeerRange(peer.id, peer.base, peer.height)
   107  		}
   108  	}()
   109  
   110  	// Start a goroutine to pull blocks
   111  	go func() {
   112  		for {
   113  			if !pool.IsRunning() {
   114  				return
   115  			}
   116  			first, second := pool.PeekTwoBlocks()
   117  			if first != nil && second != nil {
   118  				pool.PopRequest()
   119  			} else {
   120  				time.Sleep(1 * time.Second)
   121  			}
   122  		}
   123  	}()
   124  
   125  	// Pull from channels
   126  	for {
   127  		select {
   128  		case err := <-errorsCh:
   129  			t.Error(err)
   130  		case request := <-requestsCh:
   131  			t.Logf("Pulled new BlockRequest %v", request)
   132  			if request.Height == 300 {
   133  				return // Done!
   134  			}
   135  
   136  			peers[request.PeerID].inputChan <- inputData{t, pool, request}
   137  		}
   138  	}
   139  }
   140  
   141  func TestBlockPoolTimeout(t *testing.T) {
   142  	start := int64(42)
   143  	peers := makePeers(10, start+1, 1000)
   144  	errorsCh := make(chan peerError, 1000)
   145  	requestsCh := make(chan BlockRequest, 1000)
   146  	pool := NewBlockPool(start, requestsCh, errorsCh)
   147  	pool.SetLogger(log.TestingLogger())
   148  	err := pool.Start()
   149  	if err != nil {
   150  		t.Error(err)
   151  	}
   152  	t.Cleanup(func() {
   153  		if err := pool.Stop(); err != nil {
   154  			t.Error(err)
   155  		}
   156  	})
   157  
   158  	for _, peer := range peers {
   159  		t.Logf("Peer %v", peer.id)
   160  	}
   161  
   162  	// Introduce each peer.
   163  	go func() {
   164  		for _, peer := range peers {
   165  			pool.SetPeerRange(peer.id, peer.base, peer.height)
   166  		}
   167  	}()
   168  
   169  	// Start a goroutine to pull blocks
   170  	go func() {
   171  		for {
   172  			if !pool.IsRunning() {
   173  				return
   174  			}
   175  			first, second := pool.PeekTwoBlocks()
   176  			if first != nil && second != nil {
   177  				pool.PopRequest()
   178  			} else {
   179  				time.Sleep(1 * time.Second)
   180  			}
   181  		}
   182  	}()
   183  
   184  	// Pull from channels
   185  	counter := 0
   186  	timedOut := map[p2p.ID]struct{}{}
   187  	for {
   188  		select {
   189  		case err := <-errorsCh:
   190  			t.Log(err)
   191  			// consider error to be always timeout here
   192  			if _, ok := timedOut[err.peerID]; !ok {
   193  				counter++
   194  				if counter == len(peers) {
   195  					return // Done!
   196  				}
   197  			}
   198  		case request := <-requestsCh:
   199  			t.Logf("Pulled new BlockRequest %+v", request)
   200  		}
   201  	}
   202  }
   203  
   204  func TestBlockPoolRemovePeer(t *testing.T) {
   205  	peers := make(testPeers, 10)
   206  	for i := 0; i < 10; i++ {
   207  		peerID := p2p.ID(fmt.Sprintf("%d", i+1))
   208  		height := int64(i + 1)
   209  		peers[peerID] = testPeer{peerID, 0, height, make(chan inputData)}
   210  	}
   211  	requestsCh := make(chan BlockRequest)
   212  	errorsCh := make(chan peerError)
   213  
   214  	pool := NewBlockPool(1, requestsCh, errorsCh)
   215  	pool.SetLogger(log.TestingLogger())
   216  	err := pool.Start()
   217  	require.NoError(t, err)
   218  	t.Cleanup(func() {
   219  		if err := pool.Stop(); err != nil {
   220  			t.Error(err)
   221  		}
   222  	})
   223  
   224  	// add peers
   225  	for peerID, peer := range peers {
   226  		pool.SetPeerRange(peerID, peer.base, peer.height)
   227  	}
   228  	assert.EqualValues(t, 10, pool.MaxPeerHeight())
   229  
   230  	// remove not-existing peer
   231  	assert.NotPanics(t, func() { pool.RemovePeer(p2p.ID("Superman")) })
   232  
   233  	// remove peer with biggest height
   234  	pool.RemovePeer(p2p.ID("10"))
   235  	assert.EqualValues(t, 9, pool.MaxPeerHeight())
   236  
   237  	// remove all peers
   238  	for peerID := range peers {
   239  		pool.RemovePeer(peerID)
   240  	}
   241  
   242  	assert.EqualValues(t, 0, pool.MaxPeerHeight())
   243  }