github.com/evdatsion/aphelion-dpos-bft@v0.32.1/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  	cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common"
    12  	"github.com/evdatsion/aphelion-dpos-bft/libs/log"
    13  	"github.com/evdatsion/aphelion-dpos-bft/p2p"
    14  	"github.com/evdatsion/aphelion-dpos-bft/types"
    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/evdatsion/aphelion-dpos-bft/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(cmn.RandStr(12))
    68  		height := minHeight + cmn.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  	start := int64(42)
    76  	peers := makePeers(10, start+1, 1000)
    77  	errorsCh := make(chan peerError, 1000)
    78  	requestsCh := make(chan BlockRequest, 1000)
    79  	pool := NewBlockPool(start, requestsCh, errorsCh)
    80  	pool.SetLogger(log.TestingLogger())
    81  
    82  	err := pool.Start()
    83  	if err != nil {
    84  		t.Error(err)
    85  	}
    86  
    87  	defer pool.Stop()
    88  
    89  	peers.start()
    90  	defer peers.stop()
    91  
    92  	// Introduce each peer.
    93  	go func() {
    94  		for _, peer := range peers {
    95  			pool.SetPeerHeight(peer.id, peer.height)
    96  		}
    97  	}()
    98  
    99  	// Start a goroutine to pull blocks
   100  	go func() {
   101  		for {
   102  			if !pool.IsRunning() {
   103  				return
   104  			}
   105  			first, second := pool.PeekTwoBlocks()
   106  			if first != nil && second != nil {
   107  				pool.PopRequest()
   108  			} else {
   109  				time.Sleep(1 * time.Second)
   110  			}
   111  		}
   112  	}()
   113  
   114  	// Pull from channels
   115  	for {
   116  		select {
   117  		case err := <-errorsCh:
   118  			t.Error(err)
   119  		case request := <-requestsCh:
   120  			t.Logf("Pulled new BlockRequest %v", request)
   121  			if request.Height == 300 {
   122  				return // Done!
   123  			}
   124  
   125  			peers[request.PeerID].inputChan <- inputData{t, pool, request}
   126  		}
   127  	}
   128  }
   129  
   130  func TestBlockPoolTimeout(t *testing.T) {
   131  	start := int64(42)
   132  	peers := makePeers(10, start+1, 1000)
   133  	errorsCh := make(chan peerError, 1000)
   134  	requestsCh := make(chan BlockRequest, 1000)
   135  	pool := NewBlockPool(start, requestsCh, errorsCh)
   136  	pool.SetLogger(log.TestingLogger())
   137  	err := pool.Start()
   138  	if err != nil {
   139  		t.Error(err)
   140  	}
   141  	defer pool.Stop()
   142  
   143  	for _, peer := range peers {
   144  		t.Logf("Peer %v", peer.id)
   145  	}
   146  
   147  	// Introduce each peer.
   148  	go func() {
   149  		for _, peer := range peers {
   150  			pool.SetPeerHeight(peer.id, peer.height)
   151  		}
   152  	}()
   153  
   154  	// Start a goroutine to pull blocks
   155  	go func() {
   156  		for {
   157  			if !pool.IsRunning() {
   158  				return
   159  			}
   160  			first, second := pool.PeekTwoBlocks()
   161  			if first != nil && second != nil {
   162  				pool.PopRequest()
   163  			} else {
   164  				time.Sleep(1 * time.Second)
   165  			}
   166  		}
   167  	}()
   168  
   169  	// Pull from channels
   170  	counter := 0
   171  	timedOut := map[p2p.ID]struct{}{}
   172  	for {
   173  		select {
   174  		case err := <-errorsCh:
   175  			t.Log(err)
   176  			// consider error to be always timeout here
   177  			if _, ok := timedOut[err.peerID]; !ok {
   178  				counter++
   179  				if counter == len(peers) {
   180  					return // Done!
   181  				}
   182  			}
   183  		case request := <-requestsCh:
   184  			t.Logf("Pulled new BlockRequest %+v", request)
   185  		}
   186  	}
   187  }
   188  
   189  func TestBlockPoolRemovePeer(t *testing.T) {
   190  	peers := make(testPeers, 10)
   191  	for i := 0; i < 10; i++ {
   192  		peerID := p2p.ID(fmt.Sprintf("%d", i+1))
   193  		height := int64(i + 1)
   194  		peers[peerID] = testPeer{peerID, height, make(chan inputData)}
   195  	}
   196  	requestsCh := make(chan BlockRequest)
   197  	errorsCh := make(chan peerError)
   198  
   199  	pool := NewBlockPool(1, requestsCh, errorsCh)
   200  	pool.SetLogger(log.TestingLogger())
   201  	err := pool.Start()
   202  	require.NoError(t, err)
   203  	defer pool.Stop()
   204  
   205  	// add peers
   206  	for peerID, peer := range peers {
   207  		pool.SetPeerHeight(peerID, peer.height)
   208  	}
   209  	assert.EqualValues(t, 10, pool.MaxPeerHeight())
   210  
   211  	// remove not-existing peer
   212  	assert.NotPanics(t, func() { pool.RemovePeer(p2p.ID("Superman")) })
   213  
   214  	// remove peer with biggest height
   215  	pool.RemovePeer(p2p.ID("10"))
   216  	assert.EqualValues(t, 9, pool.MaxPeerHeight())
   217  
   218  	// remove all peers
   219  	for peerID := range peers {
   220  		pool.RemovePeer(peerID)
   221  	}
   222  
   223  	assert.EqualValues(t, 0, pool.MaxPeerHeight())
   224  }