github.com/aakash4dev/cometbft@v0.38.2/blocksync/pool_test.go (about)

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