github.com/vipernet-xyz/tm@v0.34.24/blockchain/v0/pool_test.go (about)

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