- package blocksync
-
- import (
- "context"
- "fmt"
- mrand "math/rand"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/tendermint/tendermint/libs/log"
- tmrand "github.com/tendermint/tendermint/libs/rand"
- "github.com/tendermint/tendermint/types"
- )
-
- func init() {
- peerTimeout = 2 * time.Second
- }
-
- type testPeer struct {
- id types.NodeID
- base int64
- height int64
- inputChan chan inputData // make sure each peer's data is sequential
- }
-
- type inputData struct {
- t *testing.T
- pool *BlockPool
- request BlockRequest
- }
-
- func (p testPeer) runInputRoutine() {
- go func() {
- for input := range p.inputChan {
- p.simulateInput(input)
- }
- }()
- }
-
- // Request desired, pretend like we got the block immediately.
- func (p testPeer) simulateInput(input inputData) {
- block := &types.Block{Header: types.Header{Height: input.request.Height}}
- input.pool.AddBlock(input.request.PeerID, block, 123)
- // TODO: uncommenting this creates a race which is detected by:
- // https://github.com/golang/go/blob/2bd767b1022dd3254bcec469f0ee164024726486/src/testing/testing.go#L854-L856
- // see: https://github.com/tendermint/tendermint/issues/3390#issue-418379890
- // input.t.Logf("Added block from peer %v (height: %v)", input.request.PeerID, input.request.Height)
- }
-
- type testPeers map[types.NodeID]testPeer
-
- func (ps testPeers) start() {
- for _, v := range ps {
- v.runInputRoutine()
- }
- }
-
- func (ps testPeers) stop() {
- for _, v := range ps {
- close(v.inputChan)
- }
- }
-
- func makePeers(numPeers int, minHeight, maxHeight int64) testPeers {
- peers := make(testPeers, numPeers)
- for i := 0; i < numPeers; i++ {
- peerID := types.NodeID(tmrand.Str(12))
- height := minHeight + mrand.Int63n(maxHeight-minHeight)
- base := minHeight + int64(i)
- if base > height {
- base = height
- }
- peers[peerID] = testPeer{peerID, base, height, make(chan inputData, 10)}
- }
- return peers
- }
-
- func TestBlockPoolBasic(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- start := int64(42)
- peers := makePeers(10, start+1, 1000)
- errorsCh := make(chan peerError, 1000)
- requestsCh := make(chan BlockRequest, 1000)
- pool := NewBlockPool(log.NewNopLogger(), start, requestsCh, errorsCh)
-
- if err := pool.Start(ctx); err != nil {
- t.Error(err)
- }
-
- t.Cleanup(func() { cancel(); pool.Wait() })
-
- peers.start()
- defer peers.stop()
-
- // Introduce each peer.
- go func() {
- for _, peer := range peers {
- pool.SetPeerRange(peer.id, peer.base, peer.height)
- }
- }()
-
- // Start a goroutine to pull blocks
- go func() {
- for {
- if !pool.IsRunning() {
- return
- }
- first, second := pool.PeekTwoBlocks()
- if first != nil && second != nil {
- pool.PopRequest()
- } else {
- time.Sleep(1 * time.Second)
- }
- }
- }()
-
- // Pull from channels
- for {
- select {
- case err := <-errorsCh:
- t.Error(err)
- case request := <-requestsCh:
- if request.Height == 300 {
- return // Done!
- }
-
- peers[request.PeerID].inputChan <- inputData{t, pool, request}
- }
- }
- }
-
- func TestBlockPoolTimeout(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- logger := log.NewNopLogger()
-
- start := int64(42)
- peers := makePeers(10, start+1, 1000)
- errorsCh := make(chan peerError, 1000)
- requestsCh := make(chan BlockRequest, 1000)
- pool := NewBlockPool(logger, start, requestsCh, errorsCh)
- err := pool.Start(ctx)
- if err != nil {
- t.Error(err)
- }
- t.Cleanup(func() { cancel(); pool.Wait() })
-
- // Introduce each peer.
- go func() {
- for _, peer := range peers {
- pool.SetPeerRange(peer.id, peer.base, peer.height)
- }
- }()
-
- // Start a goroutine to pull blocks
- go func() {
- for {
- if !pool.IsRunning() {
- return
- }
- first, second := pool.PeekTwoBlocks()
- if first != nil && second != nil {
- pool.PopRequest()
- } else {
- time.Sleep(1 * time.Second)
- }
- }
- }()
-
- // Pull from channels
- counter := 0
- timedOut := map[types.NodeID]struct{}{}
- for {
- select {
- case err := <-errorsCh:
- // consider error to be always timeout here
- if _, ok := timedOut[err.peerID]; !ok {
- counter++
- if counter == len(peers) {
- return // Done!
- }
- }
- case request := <-requestsCh:
- logger.Debug("received request",
- "counter", counter,
- "request", request)
- }
- }
- }
-
- func TestBlockPoolRemovePeer(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- peers := make(testPeers, 10)
- for i := 0; i < 10; i++ {
- peerID := types.NodeID(fmt.Sprintf("%d", i+1))
- height := int64(i + 1)
- peers[peerID] = testPeer{peerID, 0, height, make(chan inputData)}
- }
- requestsCh := make(chan BlockRequest)
- errorsCh := make(chan peerError)
-
- pool := NewBlockPool(log.NewNopLogger(), 1, requestsCh, errorsCh)
- err := pool.Start(ctx)
- require.NoError(t, err)
- t.Cleanup(func() { cancel(); pool.Wait() })
-
- // add peers
- for peerID, peer := range peers {
- pool.SetPeerRange(peerID, peer.base, peer.height)
- }
- assert.EqualValues(t, 10, pool.MaxPeerHeight())
-
- // remove not-existing peer
- assert.NotPanics(t, func() { pool.RemovePeer(types.NodeID("Superman")) })
-
- // remove peer with biggest height
- pool.RemovePeer(types.NodeID("10"))
- assert.EqualValues(t, 9, pool.MaxPeerHeight())
-
- // remove all peers
- for peerID := range peers {
- pool.RemovePeer(peerID)
- }
-
- assert.EqualValues(t, 0, pool.MaxPeerHeight())
- }
|