github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/blockchain/pool_test.go (about)

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