github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/blockchain/v2/scheduler_test.go (about)

     1  package v2
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"sort"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/franono/tendermint/p2p"
    14  	"github.com/franono/tendermint/state"
    15  	"github.com/franono/tendermint/types"
    16  )
    17  
    18  type scTestParams struct {
    19  	peers         map[string]*scPeer
    20  	initHeight    int64
    21  	height        int64
    22  	allB          []int64
    23  	pending       map[int64]p2p.ID
    24  	pendingTime   map[int64]time.Time
    25  	received      map[int64]p2p.ID
    26  	peerTimeout   time.Duration
    27  	minRecvRate   int64
    28  	targetPending int
    29  	startTime     time.Time
    30  	syncTimeout   time.Duration
    31  }
    32  
    33  func verifyScheduler(sc *scheduler) {
    34  	missing := 0
    35  	if sc.maxHeight() >= sc.height {
    36  		missing = int(math.Min(float64(sc.targetPending), float64(sc.maxHeight()-sc.height+1)))
    37  	}
    38  	if len(sc.blockStates) != missing {
    39  		panic(fmt.Sprintf("scheduler block length %d different than target %d", len(sc.blockStates), missing))
    40  	}
    41  }
    42  
    43  func newTestScheduler(params scTestParams) *scheduler {
    44  	peers := make(map[p2p.ID]*scPeer)
    45  	var maxHeight int64
    46  
    47  	sc := newScheduler(params.initHeight, params.startTime)
    48  	if params.height != 0 {
    49  		sc.height = params.height
    50  	}
    51  
    52  	for id, peer := range params.peers {
    53  		peer.peerID = p2p.ID(id)
    54  		peers[p2p.ID(id)] = peer
    55  		if maxHeight < peer.height {
    56  			maxHeight = peer.height
    57  		}
    58  	}
    59  	for _, h := range params.allB {
    60  		sc.blockStates[h] = blockStateNew
    61  	}
    62  	for h, pid := range params.pending {
    63  		sc.blockStates[h] = blockStatePending
    64  		sc.pendingBlocks[h] = pid
    65  	}
    66  	for h, tm := range params.pendingTime {
    67  		sc.pendingTime[h] = tm
    68  	}
    69  	for h, pid := range params.received {
    70  		sc.blockStates[h] = blockStateReceived
    71  		sc.receivedBlocks[h] = pid
    72  	}
    73  
    74  	sc.peers = peers
    75  	sc.peerTimeout = params.peerTimeout
    76  	if params.syncTimeout == 0 {
    77  		sc.syncTimeout = 10 * time.Second
    78  	} else {
    79  		sc.syncTimeout = params.syncTimeout
    80  	}
    81  
    82  	if params.targetPending == 0 {
    83  		sc.targetPending = 10
    84  	} else {
    85  		sc.targetPending = params.targetPending
    86  	}
    87  
    88  	sc.minRecvRate = params.minRecvRate
    89  
    90  	verifyScheduler(sc)
    91  
    92  	return sc
    93  }
    94  
    95  func TestScInit(t *testing.T) {
    96  	var (
    97  		initHeight int64 = 5
    98  		sc               = newScheduler(initHeight, time.Now())
    99  	)
   100  	assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(initHeight))
   101  	assert.Equal(t, blockStateUnknown, sc.getStateAtHeight(initHeight+1))
   102  }
   103  
   104  func TestScMaxHeights(t *testing.T) {
   105  
   106  	tests := []struct {
   107  		name    string
   108  		sc      scheduler
   109  		wantMax int64
   110  	}{
   111  		{
   112  			name:    "no peers",
   113  			sc:      scheduler{height: 11},
   114  			wantMax: 10,
   115  		},
   116  		{
   117  			name: "one ready peer",
   118  			sc: scheduler{
   119  				initHeight: 2,
   120  				height:     3,
   121  				peers:      map[p2p.ID]*scPeer{"P1": {height: 6, state: peerStateReady}},
   122  			},
   123  			wantMax: 6,
   124  		},
   125  		{
   126  			name: "ready and removed peers",
   127  			sc: scheduler{
   128  				height: 1,
   129  				peers: map[p2p.ID]*scPeer{
   130  					"P1": {height: 4, state: peerStateReady},
   131  					"P2": {height: 10, state: peerStateRemoved}},
   132  			},
   133  			wantMax: 4,
   134  		},
   135  		{
   136  			name: "removed peers",
   137  			sc: scheduler{
   138  				height: 1,
   139  				peers: map[p2p.ID]*scPeer{
   140  					"P1": {height: 4, state: peerStateRemoved},
   141  					"P2": {height: 10, state: peerStateRemoved}},
   142  			},
   143  			wantMax: 0,
   144  		},
   145  		{
   146  			name: "new peers",
   147  			sc: scheduler{
   148  				height: 1,
   149  				peers: map[p2p.ID]*scPeer{
   150  					"P1": {base: -1, height: -1, state: peerStateNew},
   151  					"P2": {base: -1, height: -1, state: peerStateNew}},
   152  			},
   153  			wantMax: 0,
   154  		},
   155  		{
   156  			name: "mixed peers",
   157  			sc: scheduler{
   158  				height: 1,
   159  				peers: map[p2p.ID]*scPeer{
   160  					"P1": {height: -1, state: peerStateNew},
   161  					"P2": {height: 10, state: peerStateReady},
   162  					"P3": {height: 20, state: peerStateRemoved},
   163  					"P4": {height: 22, state: peerStateReady},
   164  				},
   165  			},
   166  			wantMax: 22,
   167  		},
   168  	}
   169  
   170  	for _, tt := range tests {
   171  		tt := tt
   172  		t.Run(tt.name, func(t *testing.T) {
   173  			// maxHeight() should not mutate the scheduler
   174  			wantSc := tt.sc
   175  
   176  			resMax := tt.sc.maxHeight()
   177  			assert.Equal(t, tt.wantMax, resMax)
   178  			assert.Equal(t, wantSc, tt.sc)
   179  		})
   180  	}
   181  }
   182  
   183  func TestScEnsurePeer(t *testing.T) {
   184  
   185  	type args struct {
   186  		peerID p2p.ID
   187  	}
   188  	tests := []struct {
   189  		name       string
   190  		fields     scTestParams
   191  		args       args
   192  		wantFields scTestParams
   193  	}{
   194  		{
   195  			name:       "add first peer",
   196  			fields:     scTestParams{},
   197  			args:       args{peerID: "P1"},
   198  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {base: -1, height: -1, state: peerStateNew}}},
   199  		},
   200  		{
   201  			name:   "add second peer",
   202  			fields: scTestParams{peers: map[string]*scPeer{"P1": {base: -1, height: -1, state: peerStateNew}}},
   203  			args:   args{peerID: "P2"},
   204  			wantFields: scTestParams{peers: map[string]*scPeer{
   205  				"P1": {base: -1, height: -1, state: peerStateNew},
   206  				"P2": {base: -1, height: -1, state: peerStateNew}}},
   207  		},
   208  		{
   209  			name:       "add duplicate peer is fine",
   210  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
   211  			args:       args{peerID: "P1"},
   212  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
   213  		},
   214  		{
   215  			name: "add duplicate peer with existing peer in Ready state is noop",
   216  			fields: scTestParams{
   217  				peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 3}},
   218  				allB:  []int64{1, 2, 3},
   219  			},
   220  			args: args{peerID: "P1"},
   221  			wantFields: scTestParams{
   222  				peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 3}},
   223  				allB:  []int64{1, 2, 3},
   224  			},
   225  		},
   226  	}
   227  
   228  	for _, tt := range tests {
   229  		tt := tt
   230  		t.Run(tt.name, func(t *testing.T) {
   231  			sc := newTestScheduler(tt.fields)
   232  			sc.ensurePeer(tt.args.peerID)
   233  			wantSc := newTestScheduler(tt.wantFields)
   234  			assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers)
   235  		})
   236  	}
   237  }
   238  
   239  func TestScTouchPeer(t *testing.T) {
   240  	now := time.Now()
   241  
   242  	type args struct {
   243  		peerID p2p.ID
   244  		time   time.Time
   245  	}
   246  
   247  	tests := []struct {
   248  		name       string
   249  		fields     scTestParams
   250  		args       args
   251  		wantFields scTestParams
   252  		wantErr    bool
   253  	}{
   254  		{
   255  			name: "attempt to touch non existing peer",
   256  			fields: scTestParams{
   257  				peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 5}},
   258  				allB:  []int64{1, 2, 3, 4, 5},
   259  			},
   260  			args: args{peerID: "P2", time: now},
   261  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 5}},
   262  				allB: []int64{1, 2, 3, 4, 5},
   263  			},
   264  			wantErr: true,
   265  		},
   266  		{
   267  			name:       "attempt to touch peer in state New",
   268  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {}}},
   269  			args:       args{peerID: "P1", time: now},
   270  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {}}},
   271  			wantErr:    true,
   272  		},
   273  		{
   274  			name:       "attempt to touch peer in state Removed",
   275  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved}, "P2": {state: peerStateReady}}},
   276  			args:       args{peerID: "P1", time: now},
   277  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved}, "P2": {state: peerStateReady}}},
   278  			wantErr:    true,
   279  		},
   280  		{
   281  			name:   "touch peer in state Ready",
   282  			fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, lastTouched: now}}},
   283  			args:   args{peerID: "P1", time: now.Add(3 * time.Second)},
   284  			wantFields: scTestParams{peers: map[string]*scPeer{
   285  				"P1": {state: peerStateReady, lastTouched: now.Add(3 * time.Second)}}},
   286  		},
   287  	}
   288  
   289  	for _, tt := range tests {
   290  		tt := tt
   291  		t.Run(tt.name, func(t *testing.T) {
   292  			sc := newTestScheduler(tt.fields)
   293  			if err := sc.touchPeer(tt.args.peerID, tt.args.time); (err != nil) != tt.wantErr {
   294  				t.Errorf("touchPeer() wantErr %v, error = %v", tt.wantErr, err)
   295  			}
   296  			wantSc := newTestScheduler(tt.wantFields)
   297  			assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers)
   298  		})
   299  	}
   300  }
   301  
   302  func TestScPrunablePeers(t *testing.T) {
   303  	now := time.Now()
   304  
   305  	type args struct {
   306  		threshold time.Duration
   307  		time      time.Time
   308  		minSpeed  int64
   309  	}
   310  
   311  	tests := []struct {
   312  		name       string
   313  		fields     scTestParams
   314  		args       args
   315  		wantResult []p2p.ID
   316  	}{
   317  		{
   318  			name:       "no peers",
   319  			fields:     scTestParams{peers: map[string]*scPeer{}},
   320  			args:       args{threshold: time.Second, time: now.Add(time.Second + time.Millisecond), minSpeed: 100},
   321  			wantResult: []p2p.ID{},
   322  		},
   323  		{
   324  			name: "mixed peers",
   325  			fields: scTestParams{peers: map[string]*scPeer{
   326  				// X - removed, active, fast
   327  				"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101},
   328  				// X - ready, active, fast
   329  				"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101},
   330  				// X - removed, active, equal
   331  				"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100},
   332  				// V - ready, inactive, equal
   333  				"P4": {state: peerStateReady, lastTouched: now, lastRate: 100},
   334  				// V - ready, inactive, slow
   335  				"P5": {state: peerStateReady, lastTouched: now, lastRate: 99},
   336  				//  V - ready, active, slow
   337  				"P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90},
   338  			}},
   339  			args:       args{threshold: time.Second, time: now.Add(time.Second + time.Millisecond), minSpeed: 100},
   340  			wantResult: []p2p.ID{"P4", "P5", "P6"},
   341  		},
   342  	}
   343  
   344  	for _, tt := range tests {
   345  		tt := tt
   346  		t.Run(tt.name, func(t *testing.T) {
   347  			sc := newTestScheduler(tt.fields)
   348  			// peersSlowerThan should not mutate the scheduler
   349  			wantSc := sc
   350  			res := sc.prunablePeers(tt.args.threshold, tt.args.minSpeed, tt.args.time)
   351  			assert.Equal(t, tt.wantResult, res)
   352  			assert.Equal(t, wantSc, sc)
   353  		})
   354  	}
   355  }
   356  
   357  func TestScRemovePeer(t *testing.T) {
   358  
   359  	type args struct {
   360  		peerID p2p.ID
   361  	}
   362  	tests := []struct {
   363  		name       string
   364  		fields     scTestParams
   365  		args       args
   366  		wantFields scTestParams
   367  		wantErr    bool
   368  	}{
   369  		{
   370  			name:       "remove non existing peer",
   371  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
   372  			args:       args{peerID: "P2"},
   373  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
   374  		},
   375  		{
   376  			name:       "remove single New peer",
   377  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
   378  			args:       args{peerID: "P1"},
   379  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateRemoved}}},
   380  		},
   381  		{
   382  			name:       "remove one of two New peers",
   383  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: -1}, "P2": {height: -1}}},
   384  			args:       args{peerID: "P1"},
   385  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateRemoved}, "P2": {height: -1}}},
   386  		},
   387  		{
   388  			name: "remove one Ready peer, all peers removed",
   389  			fields: scTestParams{
   390  				peers: map[string]*scPeer{
   391  					"P1": {height: 10, state: peerStateRemoved},
   392  					"P2": {height: 5, state: peerStateReady}},
   393  				allB: []int64{1, 2, 3, 4, 5},
   394  			},
   395  			args: args{peerID: "P2"},
   396  			wantFields: scTestParams{peers: map[string]*scPeer{
   397  				"P1": {height: 10, state: peerStateRemoved},
   398  				"P2": {height: 5, state: peerStateRemoved}},
   399  			},
   400  		},
   401  		{
   402  			name: "attempt to remove already removed peer",
   403  			fields: scTestParams{
   404  				height: 8,
   405  				peers: map[string]*scPeer{
   406  					"P1": {height: 10, state: peerStateRemoved},
   407  					"P2": {height: 11, state: peerStateReady}},
   408  				allB: []int64{8, 9, 10, 11},
   409  			},
   410  			args: args{peerID: "P1"},
   411  			wantFields: scTestParams{
   412  				height: 8,
   413  				peers: map[string]*scPeer{
   414  					"P1": {height: 10, state: peerStateRemoved},
   415  					"P2": {height: 11, state: peerStateReady}},
   416  				allB: []int64{8, 9, 10, 11}},
   417  			wantErr: true,
   418  		},
   419  		{
   420  			name: "remove Ready peer with blocks requested",
   421  			fields: scTestParams{
   422  				peers:   map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
   423  				allB:    []int64{1, 2, 3},
   424  				pending: map[int64]p2p.ID{1: "P1"},
   425  			},
   426  			args: args{peerID: "P1"},
   427  			wantFields: scTestParams{
   428  				peers:   map[string]*scPeer{"P1": {height: 3, state: peerStateRemoved}},
   429  				allB:    []int64{},
   430  				pending: map[int64]p2p.ID{},
   431  			},
   432  		},
   433  		{
   434  			name: "remove Ready peer with blocks received",
   435  			fields: scTestParams{
   436  				peers:    map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
   437  				allB:     []int64{1, 2, 3},
   438  				received: map[int64]p2p.ID{1: "P1"},
   439  			},
   440  			args: args{peerID: "P1"},
   441  			wantFields: scTestParams{
   442  				peers:    map[string]*scPeer{"P1": {height: 3, state: peerStateRemoved}},
   443  				allB:     []int64{},
   444  				received: map[int64]p2p.ID{},
   445  			},
   446  		},
   447  		{
   448  			name: "remove Ready peer with blocks received and requested (not yet received)",
   449  			fields: scTestParams{
   450  				peers:    map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   451  				allB:     []int64{1, 2, 3, 4},
   452  				pending:  map[int64]p2p.ID{1: "P1", 3: "P1"},
   453  				received: map[int64]p2p.ID{2: "P1", 4: "P1"},
   454  			},
   455  			args: args{peerID: "P1"},
   456  			wantFields: scTestParams{
   457  				peers:    map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}},
   458  				allB:     []int64{},
   459  				pending:  map[int64]p2p.ID{},
   460  				received: map[int64]p2p.ID{},
   461  			},
   462  		},
   463  		{
   464  			name: "remove Ready peer from multiple peers set, with blocks received and requested (not yet received)",
   465  			fields: scTestParams{
   466  				peers: map[string]*scPeer{
   467  					"P1": {height: 6, state: peerStateReady},
   468  					"P2": {height: 6, state: peerStateReady},
   469  				},
   470  				allB:     []int64{1, 2, 3, 4, 5, 6},
   471  				pending:  map[int64]p2p.ID{1: "P1", 3: "P2", 6: "P1"},
   472  				received: map[int64]p2p.ID{2: "P1", 4: "P2", 5: "P2"},
   473  			},
   474  			args: args{peerID: "P1"},
   475  			wantFields: scTestParams{
   476  				peers: map[string]*scPeer{
   477  					"P1": {height: 6, state: peerStateRemoved},
   478  					"P2": {height: 6, state: peerStateReady},
   479  				},
   480  				allB:     []int64{1, 2, 3, 4, 5, 6},
   481  				pending:  map[int64]p2p.ID{3: "P2"},
   482  				received: map[int64]p2p.ID{4: "P2", 5: "P2"},
   483  			},
   484  		},
   485  	}
   486  
   487  	for _, tt := range tests {
   488  		tt := tt
   489  		t.Run(tt.name, func(t *testing.T) {
   490  			sc := newTestScheduler(tt.fields)
   491  			if err := sc.removePeer(tt.args.peerID); (err != nil) != tt.wantErr {
   492  				t.Errorf("removePeer() wantErr %v, error = %v", tt.wantErr, err)
   493  			}
   494  			wantSc := newTestScheduler(tt.wantFields)
   495  			assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers)
   496  		})
   497  	}
   498  }
   499  
   500  func TestScSetPeerRange(t *testing.T) {
   501  
   502  	type args struct {
   503  		peerID p2p.ID
   504  		base   int64
   505  		height int64
   506  	}
   507  	tests := []struct {
   508  		name       string
   509  		fields     scTestParams
   510  		args       args
   511  		wantFields scTestParams
   512  		wantErr    bool
   513  	}{
   514  		{
   515  			name: "change height of non existing peer",
   516  			fields: scTestParams{
   517  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   518  				allB:  []int64{1, 2}},
   519  			args: args{peerID: "P2", height: 4},
   520  			wantFields: scTestParams{
   521  				peers: map[string]*scPeer{
   522  					"P1": {height: 2, state: peerStateReady},
   523  					"P2": {height: 4, state: peerStateReady},
   524  				},
   525  				allB: []int64{1, 2, 3, 4}},
   526  		},
   527  		{
   528  			name: "increase height of removed peer",
   529  			fields: scTestParams{
   530  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
   531  			args:       args{peerID: "P1", height: 4},
   532  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
   533  			wantErr:    true,
   534  		},
   535  		{
   536  			name: "decrease height of single peer",
   537  			fields: scTestParams{
   538  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   539  				allB:  []int64{1, 2, 3, 4}},
   540  			args: args{peerID: "P1", height: 2},
   541  			wantFields: scTestParams{
   542  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}},
   543  				allB:  []int64{}},
   544  			wantErr: true,
   545  		},
   546  		{
   547  			name: "increase height of single peer",
   548  			fields: scTestParams{
   549  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   550  				allB:  []int64{1, 2}},
   551  			args: args{peerID: "P1", height: 4},
   552  			wantFields: scTestParams{
   553  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   554  				allB:  []int64{1, 2, 3, 4}},
   555  		},
   556  		{
   557  			name: "noop height change of single peer",
   558  			fields: scTestParams{
   559  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   560  				allB:  []int64{1, 2, 3, 4}},
   561  			args: args{peerID: "P1", height: 4},
   562  			wantFields: scTestParams{
   563  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   564  				allB:  []int64{1, 2, 3, 4}},
   565  		},
   566  		{
   567  			name: "add peer with huge height 10**10 ",
   568  			fields: scTestParams{
   569  				peers:         map[string]*scPeer{"P2": {height: -1, state: peerStateNew}},
   570  				targetPending: 4,
   571  			},
   572  			args: args{peerID: "P2", height: 10000000000},
   573  			wantFields: scTestParams{
   574  				targetPending: 4,
   575  				peers:         map[string]*scPeer{"P2": {height: 10000000000, state: peerStateReady}},
   576  				allB:          []int64{1, 2, 3, 4}},
   577  		},
   578  		{
   579  			name: "add peer with base > height should error",
   580  			fields: scTestParams{
   581  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   582  				allB:  []int64{1, 2, 3, 4}},
   583  			args: args{peerID: "P1", base: 6, height: 5},
   584  			wantFields: scTestParams{
   585  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   586  				allB:  []int64{1, 2, 3, 4}},
   587  			wantErr: true,
   588  		},
   589  		{
   590  			name: "add peer with base == height is fine",
   591  			fields: scTestParams{
   592  				peers:         map[string]*scPeer{"P1": {height: 4, state: peerStateNew}},
   593  				targetPending: 4,
   594  			},
   595  			args: args{peerID: "P1", base: 6, height: 6},
   596  			wantFields: scTestParams{
   597  				targetPending: 4,
   598  				peers:         map[string]*scPeer{"P1": {base: 6, height: 6, state: peerStateReady}},
   599  				allB:          []int64{1, 2, 3, 4}},
   600  		},
   601  	}
   602  
   603  	for _, tt := range tests {
   604  		tt := tt
   605  		t.Run(tt.name, func(t *testing.T) {
   606  			sc := newTestScheduler(tt.fields)
   607  			err := sc.setPeerRange(tt.args.peerID, tt.args.base, tt.args.height)
   608  			if (err != nil) != tt.wantErr {
   609  				t.Errorf("setPeerHeight() wantErr %v, error = %v", tt.wantErr, err)
   610  			}
   611  			wantSc := newTestScheduler(tt.wantFields)
   612  			assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers)
   613  		})
   614  	}
   615  }
   616  
   617  func TestScGetPeersWithHeight(t *testing.T) {
   618  
   619  	type args struct {
   620  		height int64
   621  	}
   622  	tests := []struct {
   623  		name       string
   624  		fields     scTestParams
   625  		args       args
   626  		wantResult []p2p.ID
   627  	}{
   628  		{
   629  			name:       "no peers",
   630  			fields:     scTestParams{peers: map[string]*scPeer{}},
   631  			args:       args{height: 10},
   632  			wantResult: []p2p.ID{},
   633  		},
   634  		{
   635  			name:       "only new peers",
   636  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
   637  			args:       args{height: 10},
   638  			wantResult: []p2p.ID{},
   639  		},
   640  		{
   641  			name:       "only Removed peers",
   642  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
   643  			args:       args{height: 2},
   644  			wantResult: []p2p.ID{},
   645  		},
   646  		{
   647  			name: "one Ready shorter peer",
   648  			fields: scTestParams{
   649  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   650  				allB:  []int64{1, 2, 3, 4},
   651  			},
   652  			args:       args{height: 5},
   653  			wantResult: []p2p.ID{},
   654  		},
   655  		{
   656  			name: "one Ready equal peer",
   657  			fields: scTestParams{
   658  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   659  				allB:  []int64{1, 2, 3, 4},
   660  			},
   661  			args:       args{height: 4},
   662  			wantResult: []p2p.ID{"P1"},
   663  		},
   664  		{
   665  			name: "one Ready higher peer",
   666  			fields: scTestParams{
   667  				targetPending: 4,
   668  				peers:         map[string]*scPeer{"P1": {height: 20, state: peerStateReady}},
   669  				allB:          []int64{1, 2, 3, 4},
   670  			},
   671  			args:       args{height: 4},
   672  			wantResult: []p2p.ID{"P1"},
   673  		},
   674  		{
   675  			name: "one Ready higher peer at base",
   676  			fields: scTestParams{
   677  				targetPending: 4,
   678  				peers:         map[string]*scPeer{"P1": {base: 4, height: 20, state: peerStateReady}},
   679  				allB:          []int64{1, 2, 3, 4},
   680  			},
   681  			args:       args{height: 4},
   682  			wantResult: []p2p.ID{"P1"},
   683  		},
   684  		{
   685  			name: "one Ready higher peer with higher base",
   686  			fields: scTestParams{
   687  				targetPending: 4,
   688  				peers:         map[string]*scPeer{"P1": {base: 10, height: 20, state: peerStateReady}},
   689  				allB:          []int64{1, 2, 3, 4},
   690  			},
   691  			args:       args{height: 4},
   692  			wantResult: []p2p.ID{},
   693  		},
   694  		{
   695  			name: "multiple mixed peers",
   696  			fields: scTestParams{
   697  				height: 8,
   698  				peers: map[string]*scPeer{
   699  					"P1": {height: -1, state: peerStateNew},
   700  					"P2": {height: 10, state: peerStateReady},
   701  					"P3": {height: 5, state: peerStateReady},
   702  					"P4": {height: 20, state: peerStateRemoved},
   703  					"P5": {height: 11, state: peerStateReady}},
   704  				allB: []int64{8, 9, 10, 11},
   705  			},
   706  			args:       args{height: 8},
   707  			wantResult: []p2p.ID{"P2", "P5"},
   708  		},
   709  	}
   710  
   711  	for _, tt := range tests {
   712  		tt := tt
   713  		t.Run(tt.name, func(t *testing.T) {
   714  			sc := newTestScheduler(tt.fields)
   715  			// getPeersWithHeight should not mutate the scheduler
   716  			wantSc := sc
   717  			res := sc.getPeersWithHeight(tt.args.height)
   718  			sort.Sort(PeerByID(res))
   719  			assert.Equal(t, tt.wantResult, res)
   720  			assert.Equal(t, wantSc, sc)
   721  		})
   722  	}
   723  }
   724  
   725  func TestScMarkPending(t *testing.T) {
   726  	now := time.Now()
   727  
   728  	type args struct {
   729  		peerID p2p.ID
   730  		height int64
   731  		tm     time.Time
   732  	}
   733  	tests := []struct {
   734  		name       string
   735  		fields     scTestParams
   736  		args       args
   737  		wantFields scTestParams
   738  		wantErr    bool
   739  	}{
   740  		{
   741  			name: "attempt mark pending an unknown block above height",
   742  			fields: scTestParams{
   743  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   744  				allB:  []int64{1, 2}},
   745  			args: args{peerID: "P1", height: 3, tm: now},
   746  			wantFields: scTestParams{
   747  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   748  				allB:  []int64{1, 2}},
   749  			wantErr: true,
   750  		},
   751  		{
   752  			name: "attempt mark pending an unknown block below base",
   753  			fields: scTestParams{
   754  				peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
   755  				allB:  []int64{1, 2, 3, 4, 5, 6}},
   756  			args: args{peerID: "P1", height: 3, tm: now},
   757  			wantFields: scTestParams{
   758  				peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
   759  				allB:  []int64{1, 2, 3, 4, 5, 6}},
   760  			wantErr: true,
   761  		},
   762  		{
   763  			name: "attempt mark pending from non existing peer",
   764  			fields: scTestParams{
   765  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   766  				allB:  []int64{1, 2}},
   767  			args: args{peerID: "P2", height: 1, tm: now},
   768  			wantFields: scTestParams{
   769  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   770  				allB:  []int64{1, 2}},
   771  			wantErr: true,
   772  		},
   773  		{
   774  			name: "mark pending from Removed peer",
   775  			fields: scTestParams{
   776  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
   777  			args: args{peerID: "P1", height: 1, tm: now},
   778  			wantFields: scTestParams{
   779  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
   780  			wantErr: true,
   781  		},
   782  		{
   783  			name: "mark pending from New peer",
   784  			fields: scTestParams{
   785  				peers: map[string]*scPeer{
   786  					"P1": {height: 4, state: peerStateReady},
   787  					"P2": {height: 4, state: peerStateNew},
   788  				},
   789  				allB: []int64{1, 2, 3, 4},
   790  			},
   791  			args: args{peerID: "P2", height: 2, tm: now},
   792  			wantFields: scTestParams{
   793  				peers: map[string]*scPeer{
   794  					"P1": {height: 4, state: peerStateReady},
   795  					"P2": {height: 4, state: peerStateNew},
   796  				},
   797  				allB: []int64{1, 2, 3, 4},
   798  			},
   799  			wantErr: true,
   800  		},
   801  		{
   802  			name: "mark pending from short peer",
   803  			fields: scTestParams{
   804  				peers: map[string]*scPeer{
   805  					"P1": {height: 4, state: peerStateReady},
   806  					"P2": {height: 2, state: peerStateReady},
   807  				},
   808  				allB: []int64{1, 2, 3, 4},
   809  			},
   810  			args: args{peerID: "P2", height: 3, tm: now},
   811  			wantFields: scTestParams{
   812  				peers: map[string]*scPeer{
   813  					"P1": {height: 4, state: peerStateReady},
   814  					"P2": {height: 2, state: peerStateReady},
   815  				},
   816  				allB: []int64{1, 2, 3, 4},
   817  			},
   818  			wantErr: true,
   819  		},
   820  		{
   821  			name: "mark pending all good",
   822  			fields: scTestParams{
   823  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   824  				allB:        []int64{1, 2},
   825  				pending:     map[int64]p2p.ID{1: "P1"},
   826  				pendingTime: map[int64]time.Time{1: now},
   827  			},
   828  			args: args{peerID: "P1", height: 2, tm: now.Add(time.Millisecond)},
   829  			wantFields: scTestParams{
   830  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   831  				allB:        []int64{1, 2},
   832  				pending:     map[int64]p2p.ID{1: "P1", 2: "P1"},
   833  				pendingTime: map[int64]time.Time{1: now, 2: now.Add(time.Millisecond)},
   834  			},
   835  		},
   836  	}
   837  
   838  	for _, tt := range tests {
   839  		tt := tt
   840  		t.Run(tt.name, func(t *testing.T) {
   841  			sc := newTestScheduler(tt.fields)
   842  			if err := sc.markPending(tt.args.peerID, tt.args.height, tt.args.tm); (err != nil) != tt.wantErr {
   843  				t.Errorf("markPending() wantErr %v, error = %v", tt.wantErr, err)
   844  			}
   845  			wantSc := newTestScheduler(tt.wantFields)
   846  			assert.Equal(t, wantSc, sc)
   847  		})
   848  	}
   849  }
   850  
   851  func TestScMarkReceived(t *testing.T) {
   852  	now := time.Now()
   853  
   854  	type args struct {
   855  		peerID p2p.ID
   856  		height int64
   857  		size   int64
   858  		tm     time.Time
   859  	}
   860  	tests := []struct {
   861  		name       string
   862  		fields     scTestParams
   863  		args       args
   864  		wantFields scTestParams
   865  		wantErr    bool
   866  	}{
   867  		{
   868  			name: "received from non existing peer",
   869  			fields: scTestParams{
   870  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   871  				allB:  []int64{1, 2}},
   872  			args: args{peerID: "P2", height: 1, size: 1000, tm: now},
   873  			wantFields: scTestParams{
   874  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   875  				allB:  []int64{1, 2}},
   876  			wantErr: true,
   877  		},
   878  		{
   879  			name: "received from removed peer",
   880  			fields: scTestParams{
   881  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
   882  			args: args{peerID: "P1", height: 1, size: 1000, tm: now},
   883  			wantFields: scTestParams{
   884  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
   885  			wantErr: true,
   886  		},
   887  		{
   888  			name: "received from unsolicited peer",
   889  			fields: scTestParams{
   890  				peers: map[string]*scPeer{
   891  					"P1": {height: 4, state: peerStateReady},
   892  					"P2": {height: 4, state: peerStateReady},
   893  				},
   894  				allB:    []int64{1, 2, 3, 4},
   895  				pending: map[int64]p2p.ID{1: "P1", 2: "P2", 3: "P2", 4: "P1"},
   896  			},
   897  			args: args{peerID: "P1", height: 2, size: 1000, tm: now},
   898  			wantFields: scTestParams{
   899  				peers: map[string]*scPeer{
   900  					"P1": {height: 4, state: peerStateReady},
   901  					"P2": {height: 4, state: peerStateReady},
   902  				},
   903  				allB:    []int64{1, 2, 3, 4},
   904  				pending: map[int64]p2p.ID{1: "P1", 2: "P2", 3: "P2", 4: "P1"},
   905  			},
   906  			wantErr: true,
   907  		},
   908  		{
   909  			name: "received but blockRequest not sent",
   910  			fields: scTestParams{
   911  				peers:   map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   912  				allB:    []int64{1, 2, 3, 4},
   913  				pending: map[int64]p2p.ID{},
   914  			},
   915  			args: args{peerID: "P1", height: 2, size: 1000, tm: now},
   916  			wantFields: scTestParams{
   917  				peers:   map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   918  				allB:    []int64{1, 2, 3, 4},
   919  				pending: map[int64]p2p.ID{},
   920  			},
   921  			wantErr: true,
   922  		},
   923  		{
   924  			name: "received with bad timestamp",
   925  			fields: scTestParams{
   926  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   927  				allB:        []int64{1, 2},
   928  				pending:     map[int64]p2p.ID{1: "P1", 2: "P1"},
   929  				pendingTime: map[int64]time.Time{1: now, 2: now.Add(time.Second)},
   930  			},
   931  			args: args{peerID: "P1", height: 2, size: 1000, tm: now},
   932  			wantFields: scTestParams{
   933  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   934  				allB:        []int64{1, 2},
   935  				pending:     map[int64]p2p.ID{1: "P1", 2: "P1"},
   936  				pendingTime: map[int64]time.Time{1: now, 2: now.Add(time.Second)},
   937  			},
   938  			wantErr: true,
   939  		},
   940  		{
   941  			name: "received all good",
   942  			fields: scTestParams{
   943  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   944  				allB:        []int64{1, 2},
   945  				pending:     map[int64]p2p.ID{1: "P1", 2: "P1"},
   946  				pendingTime: map[int64]time.Time{1: now, 2: now},
   947  			},
   948  			args: args{peerID: "P1", height: 2, size: 1000, tm: now.Add(time.Millisecond)},
   949  			wantFields: scTestParams{
   950  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   951  				allB:        []int64{1, 2},
   952  				pending:     map[int64]p2p.ID{1: "P1"},
   953  				pendingTime: map[int64]time.Time{1: now},
   954  				received:    map[int64]p2p.ID{2: "P1"},
   955  			},
   956  		},
   957  	}
   958  
   959  	for _, tt := range tests {
   960  		tt := tt
   961  		t.Run(tt.name, func(t *testing.T) {
   962  			sc := newTestScheduler(tt.fields)
   963  			if err := sc.markReceived(
   964  				tt.args.peerID,
   965  				tt.args.height,
   966  				tt.args.size,
   967  				now.Add(time.Second)); (err != nil) != tt.wantErr {
   968  				t.Errorf("markReceived() wantErr %v, error = %v", tt.wantErr, err)
   969  			}
   970  			wantSc := newTestScheduler(tt.wantFields)
   971  			assert.Equal(t, wantSc, sc)
   972  		})
   973  	}
   974  }
   975  
   976  func TestScMarkProcessed(t *testing.T) {
   977  	now := time.Now()
   978  
   979  	type args struct {
   980  		height int64
   981  	}
   982  	tests := []struct {
   983  		name       string
   984  		fields     scTestParams
   985  		args       args
   986  		wantFields scTestParams
   987  		wantErr    bool
   988  	}{
   989  		{
   990  			name: "processed an unreceived block",
   991  			fields: scTestParams{
   992  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
   993  				allB:        []int64{1, 2},
   994  				pending:     map[int64]p2p.ID{2: "P1"},
   995  				pendingTime: map[int64]time.Time{2: now},
   996  				received:    map[int64]p2p.ID{1: "P1"}},
   997  			args: args{height: 2},
   998  			wantFields: scTestParams{
   999  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
  1000  				allB:        []int64{1, 2},
  1001  				pending:     map[int64]p2p.ID{2: "P1"},
  1002  				pendingTime: map[int64]time.Time{2: now},
  1003  				received:    map[int64]p2p.ID{1: "P1"}},
  1004  			wantErr: true,
  1005  		},
  1006  		{
  1007  			name: "mark processed success",
  1008  			fields: scTestParams{
  1009  				height:      1,
  1010  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
  1011  				allB:        []int64{1, 2},
  1012  				pending:     map[int64]p2p.ID{2: "P1"},
  1013  				pendingTime: map[int64]time.Time{2: now},
  1014  				received:    map[int64]p2p.ID{1: "P1"}},
  1015  			args: args{height: 1},
  1016  			wantFields: scTestParams{
  1017  				height:      2,
  1018  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
  1019  				allB:        []int64{2},
  1020  				pending:     map[int64]p2p.ID{2: "P1"},
  1021  				pendingTime: map[int64]time.Time{2: now}},
  1022  		},
  1023  	}
  1024  
  1025  	for _, tt := range tests {
  1026  		tt := tt
  1027  		t.Run(tt.name, func(t *testing.T) {
  1028  			sc := newTestScheduler(tt.fields)
  1029  			oldBlockState := sc.getStateAtHeight(tt.args.height)
  1030  			if err := sc.markProcessed(tt.args.height); (err != nil) != tt.wantErr {
  1031  				t.Errorf("markProcessed() wantErr %v, error = %v", tt.wantErr, err)
  1032  			}
  1033  			if tt.wantErr {
  1034  				assert.Equal(t, oldBlockState, sc.getStateAtHeight(tt.args.height))
  1035  			} else {
  1036  				assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(tt.args.height))
  1037  			}
  1038  			wantSc := newTestScheduler(tt.wantFields)
  1039  			checkSameScheduler(t, wantSc, sc)
  1040  		})
  1041  	}
  1042  }
  1043  
  1044  func TestScResetState(t *testing.T) {
  1045  	tests := []struct {
  1046  		name       string
  1047  		fields     scTestParams
  1048  		state      state.State
  1049  		wantFields scTestParams
  1050  	}{
  1051  		{
  1052  			name: "updates height and initHeight",
  1053  			fields: scTestParams{
  1054  				height:     0,
  1055  				initHeight: 0,
  1056  			},
  1057  			state: state.State{LastBlockHeight: 7},
  1058  			wantFields: scTestParams{
  1059  				height:     8,
  1060  				initHeight: 8,
  1061  			},
  1062  		},
  1063  	}
  1064  
  1065  	for _, tt := range tests {
  1066  		tt := tt
  1067  		t.Run(tt.name, func(t *testing.T) {
  1068  			sc := newTestScheduler(tt.fields)
  1069  			e, err := sc.handleResetState(bcResetState{state: tt.state})
  1070  			require.NoError(t, err)
  1071  			assert.Equal(t, e, noOp)
  1072  			wantSc := newTestScheduler(tt.wantFields)
  1073  			checkSameScheduler(t, wantSc, sc)
  1074  		})
  1075  	}
  1076  }
  1077  
  1078  func TestScAllBlocksProcessed(t *testing.T) {
  1079  	now := time.Now()
  1080  
  1081  	tests := []struct {
  1082  		name       string
  1083  		fields     scTestParams
  1084  		wantResult bool
  1085  	}{
  1086  		{
  1087  			name:       "no blocks, no peers",
  1088  			fields:     scTestParams{},
  1089  			wantResult: false,
  1090  		},
  1091  		{
  1092  			name: "only New blocks",
  1093  			fields: scTestParams{
  1094  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1095  				allB:  []int64{1, 2, 3, 4},
  1096  			},
  1097  			wantResult: false,
  1098  		},
  1099  		{
  1100  			name: "only Pending blocks",
  1101  			fields: scTestParams{
  1102  				peers:       map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1103  				allB:        []int64{1, 2, 3, 4},
  1104  				pending:     map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
  1105  				pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now},
  1106  			},
  1107  			wantResult: false,
  1108  		},
  1109  		{
  1110  			name: "only Received blocks",
  1111  			fields: scTestParams{
  1112  				peers:    map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1113  				allB:     []int64{1, 2, 3, 4},
  1114  				received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
  1115  			},
  1116  			wantResult: false,
  1117  		},
  1118  		{
  1119  			name: "only Processed blocks plus highest is received",
  1120  			fields: scTestParams{
  1121  				height: 4,
  1122  				peers: map[string]*scPeer{
  1123  					"P1": {height: 4, state: peerStateReady}},
  1124  				allB:     []int64{4},
  1125  				received: map[int64]p2p.ID{4: "P1"},
  1126  			},
  1127  			wantResult: true,
  1128  		},
  1129  		{
  1130  			name: "mixed block states",
  1131  			fields: scTestParams{
  1132  				peers:       map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1133  				allB:        []int64{1, 2, 3, 4},
  1134  				pending:     map[int64]p2p.ID{2: "P1", 4: "P1"},
  1135  				pendingTime: map[int64]time.Time{2: now, 4: now},
  1136  			},
  1137  			wantResult: false,
  1138  		},
  1139  	}
  1140  
  1141  	for _, tt := range tests {
  1142  		tt := tt
  1143  		t.Run(tt.name, func(t *testing.T) {
  1144  			sc := newTestScheduler(tt.fields)
  1145  			// allBlocksProcessed() should not mutate the scheduler
  1146  			wantSc := sc
  1147  			res := sc.allBlocksProcessed()
  1148  			assert.Equal(t, tt.wantResult, res)
  1149  			checkSameScheduler(t, wantSc, sc)
  1150  		})
  1151  	}
  1152  }
  1153  
  1154  func TestScNextHeightToSchedule(t *testing.T) {
  1155  	now := time.Now()
  1156  
  1157  	tests := []struct {
  1158  		name       string
  1159  		fields     scTestParams
  1160  		wantHeight int64
  1161  	}{
  1162  		{
  1163  			name:       "no blocks",
  1164  			fields:     scTestParams{initHeight: 10, height: 11},
  1165  			wantHeight: -1,
  1166  		},
  1167  		{
  1168  			name: "only New blocks",
  1169  			fields: scTestParams{
  1170  				initHeight: 2,
  1171  				height:     3,
  1172  				peers:      map[string]*scPeer{"P1": {height: 6, state: peerStateReady}},
  1173  				allB:       []int64{3, 4, 5, 6},
  1174  			},
  1175  			wantHeight: 3,
  1176  		},
  1177  		{
  1178  			name: "only Pending blocks",
  1179  			fields: scTestParams{
  1180  				height:      1,
  1181  				peers:       map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1182  				allB:        []int64{1, 2, 3, 4},
  1183  				pending:     map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
  1184  				pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now},
  1185  			},
  1186  			wantHeight: -1,
  1187  		},
  1188  		{
  1189  			name: "only Received blocks",
  1190  			fields: scTestParams{
  1191  				height:   1,
  1192  				peers:    map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1193  				allB:     []int64{1, 2, 3, 4},
  1194  				received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
  1195  			},
  1196  			wantHeight: -1,
  1197  		},
  1198  		{
  1199  			name: "only Processed blocks",
  1200  			fields: scTestParams{
  1201  				height: 1,
  1202  				peers:  map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1203  				allB:   []int64{1, 2, 3, 4},
  1204  			},
  1205  			wantHeight: 1,
  1206  		},
  1207  		{
  1208  			name: "mixed block states",
  1209  			fields: scTestParams{
  1210  				height:      1,
  1211  				peers:       map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1212  				allB:        []int64{1, 2, 3, 4},
  1213  				pending:     map[int64]p2p.ID{2: "P1"},
  1214  				pendingTime: map[int64]time.Time{2: now},
  1215  			},
  1216  			wantHeight: 1,
  1217  		},
  1218  	}
  1219  
  1220  	for _, tt := range tests {
  1221  		tt := tt
  1222  		t.Run(tt.name, func(t *testing.T) {
  1223  			sc := newTestScheduler(tt.fields)
  1224  			// nextHeightToSchedule() should not mutate the scheduler
  1225  			wantSc := sc
  1226  
  1227  			resMin := sc.nextHeightToSchedule()
  1228  			assert.Equal(t, tt.wantHeight, resMin)
  1229  			checkSameScheduler(t, wantSc, sc)
  1230  		})
  1231  	}
  1232  }
  1233  
  1234  func TestScSelectPeer(t *testing.T) {
  1235  
  1236  	type args struct {
  1237  		height int64
  1238  	}
  1239  	tests := []struct {
  1240  		name       string
  1241  		fields     scTestParams
  1242  		args       args
  1243  		wantResult p2p.ID
  1244  		wantError  bool
  1245  	}{
  1246  		{
  1247  			name:       "no peers",
  1248  			fields:     scTestParams{peers: map[string]*scPeer{}},
  1249  			args:       args{height: 10},
  1250  			wantResult: "",
  1251  			wantError:  true,
  1252  		},
  1253  		{
  1254  			name:       "only new peers",
  1255  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
  1256  			args:       args{height: 10},
  1257  			wantResult: "",
  1258  			wantError:  true,
  1259  		},
  1260  		{
  1261  			name:       "only Removed peers",
  1262  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
  1263  			args:       args{height: 2},
  1264  			wantResult: "",
  1265  			wantError:  true,
  1266  		},
  1267  		{
  1268  			name: "one Ready shorter peer",
  1269  			fields: scTestParams{
  1270  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1271  				allB:  []int64{1, 2, 3, 4},
  1272  			},
  1273  			args:       args{height: 5},
  1274  			wantResult: "",
  1275  			wantError:  true,
  1276  		},
  1277  		{
  1278  			name: "one Ready equal peer",
  1279  			fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1280  				allB: []int64{1, 2, 3, 4},
  1281  			},
  1282  			args:       args{height: 4},
  1283  			wantResult: "P1",
  1284  		},
  1285  		{
  1286  			name: "one Ready higher peer",
  1287  			fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}},
  1288  				allB: []int64{1, 2, 3, 4, 5, 6},
  1289  			},
  1290  			args:       args{height: 4},
  1291  			wantResult: "P1",
  1292  		},
  1293  		{
  1294  			name: "one Ready higher peer with higher base",
  1295  			fields: scTestParams{
  1296  				peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
  1297  				allB:  []int64{1, 2, 3, 4, 5, 6},
  1298  			},
  1299  			args:       args{height: 3},
  1300  			wantResult: "",
  1301  			wantError:  true,
  1302  		},
  1303  		{
  1304  			name: "many Ready higher peers with different number of pending requests",
  1305  			fields: scTestParams{
  1306  				height: 4,
  1307  				peers: map[string]*scPeer{
  1308  					"P1": {height: 8, state: peerStateReady},
  1309  					"P2": {height: 9, state: peerStateReady}},
  1310  				allB: []int64{4, 5, 6, 7, 8, 9},
  1311  				pending: map[int64]p2p.ID{
  1312  					4: "P1", 6: "P1",
  1313  					5: "P2",
  1314  				},
  1315  			},
  1316  			args:       args{height: 4},
  1317  			wantResult: "P2",
  1318  		},
  1319  		{
  1320  			name: "many Ready higher peers with same number of pending requests",
  1321  			fields: scTestParams{
  1322  				peers: map[string]*scPeer{
  1323  					"P2": {height: 20, state: peerStateReady},
  1324  					"P1": {height: 15, state: peerStateReady},
  1325  					"P3": {height: 15, state: peerStateReady}},
  1326  				allB: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
  1327  				pending: map[int64]p2p.ID{
  1328  					1: "P1", 2: "P1",
  1329  					3: "P3", 4: "P3",
  1330  					5: "P2", 6: "P2",
  1331  				},
  1332  			},
  1333  			args:       args{height: 7},
  1334  			wantResult: "P1",
  1335  		},
  1336  	}
  1337  
  1338  	for _, tt := range tests {
  1339  		tt := tt
  1340  		t.Run(tt.name, func(t *testing.T) {
  1341  			sc := newTestScheduler(tt.fields)
  1342  			// selectPeer should not mutate the scheduler
  1343  			wantSc := sc
  1344  			res, err := sc.selectPeer(tt.args.height)
  1345  			assert.Equal(t, tt.wantResult, res)
  1346  			assert.Equal(t, tt.wantError, err != nil)
  1347  			checkSameScheduler(t, wantSc, sc)
  1348  		})
  1349  	}
  1350  }
  1351  
  1352  // makeScBlock makes an empty block.
  1353  func makeScBlock(height int64) *types.Block {
  1354  	return &types.Block{Header: types.Header{Height: height}}
  1355  }
  1356  
  1357  // used in place of assert.Equal(t, want, actual) to avoid failures due to
  1358  // scheduler.lastAdvanced timestamp inequalities.
  1359  func checkSameScheduler(t *testing.T, want *scheduler, actual *scheduler) {
  1360  	assert.Equal(t, want.initHeight, actual.initHeight)
  1361  	assert.Equal(t, want.height, actual.height)
  1362  	assert.Equal(t, want.peers, actual.peers)
  1363  	assert.Equal(t, want.blockStates, actual.blockStates)
  1364  	assert.Equal(t, want.pendingBlocks, actual.pendingBlocks)
  1365  	assert.Equal(t, want.pendingTime, actual.pendingTime)
  1366  	assert.Equal(t, want.blockStates, actual.blockStates)
  1367  	assert.Equal(t, want.receivedBlocks, actual.receivedBlocks)
  1368  	assert.Equal(t, want.blockStates, actual.blockStates)
  1369  }
  1370  
  1371  // checkScResults checks scheduler handler test results
  1372  func checkScResults(t *testing.T, wantErr bool, err error, wantEvent Event, event Event) {
  1373  	if (err != nil) != wantErr {
  1374  		t.Errorf("error = %v, wantErr %v", err, wantErr)
  1375  		return
  1376  	}
  1377  	switch wantEvent := wantEvent.(type) {
  1378  	case scPeerError:
  1379  		assert.Equal(t, wantEvent.peerID, event.(scPeerError).peerID)
  1380  		assert.Equal(t, wantEvent.reason != nil, event.(scPeerError).reason != nil)
  1381  	case scBlockReceived:
  1382  		assert.Equal(t, wantEvent.peerID, event.(scBlockReceived).peerID)
  1383  		assert.Equal(t, wantEvent.block, event.(scBlockReceived).block)
  1384  	case scSchedulerFail:
  1385  		assert.Equal(t, wantEvent.reason != nil, event.(scSchedulerFail).reason != nil)
  1386  	}
  1387  }
  1388  
  1389  func TestScHandleBlockResponse(t *testing.T) {
  1390  	now := time.Now()
  1391  	block6FromP1 := bcBlockResponse{
  1392  		time:   now.Add(time.Millisecond),
  1393  		peerID: p2p.ID("P1"),
  1394  		size:   100,
  1395  		block:  makeScBlock(6),
  1396  	}
  1397  
  1398  	type args struct {
  1399  		event bcBlockResponse
  1400  	}
  1401  
  1402  	tests := []struct {
  1403  		name      string
  1404  		fields    scTestParams
  1405  		args      args
  1406  		wantEvent Event
  1407  		wantErr   bool
  1408  	}{
  1409  		{
  1410  			name:      "empty scheduler",
  1411  			fields:    scTestParams{},
  1412  			args:      args{event: block6FromP1},
  1413  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1414  		},
  1415  		{
  1416  			name:      "block from removed peer",
  1417  			fields:    scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1418  			args:      args{event: block6FromP1},
  1419  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1420  		},
  1421  		{
  1422  			name: "block we haven't asked for",
  1423  			fields: scTestParams{
  1424  				peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1425  				allB:  []int64{1, 2, 3, 4, 5, 6, 7, 8}},
  1426  			args:      args{event: block6FromP1},
  1427  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1428  		},
  1429  		{
  1430  			name: "block from wrong peer",
  1431  			fields: scTestParams{
  1432  				peers:       map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
  1433  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1434  				pending:     map[int64]p2p.ID{6: "P2"},
  1435  				pendingTime: map[int64]time.Time{6: now},
  1436  			},
  1437  			args:      args{event: block6FromP1},
  1438  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1439  		},
  1440  		{
  1441  			name: "block with bad timestamp",
  1442  			fields: scTestParams{
  1443  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1444  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1445  				pending:     map[int64]p2p.ID{6: "P1"},
  1446  				pendingTime: map[int64]time.Time{6: now.Add(time.Second)},
  1447  			},
  1448  			args:      args{event: block6FromP1},
  1449  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1450  		},
  1451  		{
  1452  			name: "good block, accept",
  1453  			fields: scTestParams{
  1454  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1455  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1456  				pending:     map[int64]p2p.ID{6: "P1"},
  1457  				pendingTime: map[int64]time.Time{6: now},
  1458  			},
  1459  			args:      args{event: block6FromP1},
  1460  			wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(6)},
  1461  		},
  1462  	}
  1463  
  1464  	for _, tt := range tests {
  1465  		tt := tt
  1466  		t.Run(tt.name, func(t *testing.T) {
  1467  			sc := newTestScheduler(tt.fields)
  1468  			event, err := sc.handleBlockResponse(tt.args.event)
  1469  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1470  		})
  1471  	}
  1472  }
  1473  
  1474  func TestScHandleNoBlockResponse(t *testing.T) {
  1475  	now := time.Now()
  1476  	noBlock6FromP1 := bcNoBlockResponse{
  1477  		time:   now.Add(time.Millisecond),
  1478  		peerID: p2p.ID("P1"),
  1479  		height: 6,
  1480  	}
  1481  
  1482  	tests := []struct {
  1483  		name       string
  1484  		fields     scTestParams
  1485  		wantEvent  Event
  1486  		wantFields scTestParams
  1487  		wantErr    bool
  1488  	}{
  1489  		{
  1490  			name:       "empty scheduler",
  1491  			fields:     scTestParams{},
  1492  			wantEvent:  noOpEvent{},
  1493  			wantFields: scTestParams{},
  1494  		},
  1495  		{
  1496  			name:       "noBlock from removed peer",
  1497  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1498  			wantEvent:  noOpEvent{},
  1499  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1500  		},
  1501  		{
  1502  			name: "for block we haven't asked for",
  1503  			fields: scTestParams{
  1504  				peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1505  				allB:  []int64{1, 2, 3, 4, 5, 6, 7, 8}},
  1506  			wantEvent:  scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1507  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1508  		},
  1509  		{
  1510  			name: "noBlock from peer we don't have",
  1511  			fields: scTestParams{
  1512  				peers:       map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
  1513  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1514  				pending:     map[int64]p2p.ID{6: "P2"},
  1515  				pendingTime: map[int64]time.Time{6: now},
  1516  			},
  1517  			wantEvent: noOpEvent{},
  1518  			wantFields: scTestParams{
  1519  				peers:       map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
  1520  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1521  				pending:     map[int64]p2p.ID{6: "P2"},
  1522  				pendingTime: map[int64]time.Time{6: now},
  1523  			},
  1524  		},
  1525  		{
  1526  			name: "noBlock from existing peer",
  1527  			fields: scTestParams{
  1528  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1529  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1530  				pending:     map[int64]p2p.ID{6: "P1"},
  1531  				pendingTime: map[int64]time.Time{6: now},
  1532  			},
  1533  			wantEvent:  scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1534  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1535  		},
  1536  	}
  1537  
  1538  	for _, tt := range tests {
  1539  		tt := tt
  1540  		t.Run(tt.name, func(t *testing.T) {
  1541  			sc := newTestScheduler(tt.fields)
  1542  			event, err := sc.handleNoBlockResponse(noBlock6FromP1)
  1543  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1544  			wantSc := newTestScheduler(tt.wantFields)
  1545  			assert.Equal(t, wantSc, sc)
  1546  		})
  1547  	}
  1548  }
  1549  
  1550  func TestScHandleBlockProcessed(t *testing.T) {
  1551  	now := time.Now()
  1552  	processed6FromP1 := pcBlockProcessed{
  1553  		peerID: p2p.ID("P1"),
  1554  		height: 6,
  1555  	}
  1556  
  1557  	type args struct {
  1558  		event pcBlockProcessed
  1559  	}
  1560  
  1561  	tests := []struct {
  1562  		name      string
  1563  		fields    scTestParams
  1564  		args      args
  1565  		wantEvent Event
  1566  		wantErr   bool
  1567  	}{
  1568  		{
  1569  			name:      "empty scheduler",
  1570  			fields:    scTestParams{height: 6},
  1571  			args:      args{event: processed6FromP1},
  1572  			wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")},
  1573  		},
  1574  		{
  1575  			name: "processed block we don't have",
  1576  			fields: scTestParams{
  1577  				initHeight:  5,
  1578  				height:      6,
  1579  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1580  				allB:        []int64{6, 7, 8},
  1581  				pending:     map[int64]p2p.ID{6: "P1"},
  1582  				pendingTime: map[int64]time.Time{6: now},
  1583  			},
  1584  			args:      args{event: processed6FromP1},
  1585  			wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")},
  1586  		},
  1587  		{
  1588  			name: "processed block ok, we processed all blocks",
  1589  			fields: scTestParams{
  1590  				initHeight: 5,
  1591  				height:     6,
  1592  				peers:      map[string]*scPeer{"P1": {height: 7, state: peerStateReady}},
  1593  				allB:       []int64{6, 7},
  1594  				received:   map[int64]p2p.ID{6: "P1", 7: "P1"},
  1595  			},
  1596  			args:      args{event: processed6FromP1},
  1597  			wantEvent: scFinishedEv{},
  1598  		},
  1599  		{
  1600  			name: "processed block ok, we still have blocks to process",
  1601  			fields: scTestParams{
  1602  				initHeight: 5,
  1603  				height:     6,
  1604  				peers:      map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1605  				allB:       []int64{6, 7, 8},
  1606  				pending:    map[int64]p2p.ID{7: "P1", 8: "P1"},
  1607  				received:   map[int64]p2p.ID{6: "P1"},
  1608  			},
  1609  			args:      args{event: processed6FromP1},
  1610  			wantEvent: noOpEvent{},
  1611  		},
  1612  	}
  1613  
  1614  	for _, tt := range tests {
  1615  		tt := tt
  1616  		t.Run(tt.name, func(t *testing.T) {
  1617  			sc := newTestScheduler(tt.fields)
  1618  			event, err := sc.handleBlockProcessed(tt.args.event)
  1619  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1620  		})
  1621  	}
  1622  }
  1623  
  1624  func TestScHandleBlockVerificationFailure(t *testing.T) {
  1625  	now := time.Now()
  1626  
  1627  	type args struct {
  1628  		event pcBlockVerificationFailure
  1629  	}
  1630  
  1631  	tests := []struct {
  1632  		name      string
  1633  		fields    scTestParams
  1634  		args      args
  1635  		wantEvent Event
  1636  		wantErr   bool
  1637  	}{
  1638  		{
  1639  			name:      "empty scheduler",
  1640  			fields:    scTestParams{},
  1641  			args:      args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}},
  1642  			wantEvent: noOpEvent{},
  1643  		},
  1644  		{
  1645  			name: "failed block we don't have, single peer is still removed",
  1646  			fields: scTestParams{
  1647  				initHeight:  5,
  1648  				height:      6,
  1649  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1650  				allB:        []int64{6, 7, 8},
  1651  				pending:     map[int64]p2p.ID{6: "P1"},
  1652  				pendingTime: map[int64]time.Time{6: now},
  1653  			},
  1654  			args:      args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}},
  1655  			wantEvent: scFinishedEv{},
  1656  		},
  1657  		{
  1658  			name: "failed block we don't have, one of two peers are removed",
  1659  			fields: scTestParams{
  1660  				initHeight:  5,
  1661  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}},
  1662  				allB:        []int64{6, 7, 8},
  1663  				pending:     map[int64]p2p.ID{6: "P1"},
  1664  				pendingTime: map[int64]time.Time{6: now},
  1665  			},
  1666  			args:      args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}},
  1667  			wantEvent: noOpEvent{},
  1668  		},
  1669  		{
  1670  			name: "failed block, all blocks are processed after removal",
  1671  			fields: scTestParams{
  1672  				initHeight: 5,
  1673  				height:     6,
  1674  				peers:      map[string]*scPeer{"P1": {height: 7, state: peerStateReady}},
  1675  				allB:       []int64{6, 7},
  1676  				received:   map[int64]p2p.ID{6: "P1", 7: "P1"},
  1677  			},
  1678  			args:      args{event: pcBlockVerificationFailure{height: 7, firstPeerID: "P1", secondPeerID: "P1"}},
  1679  			wantEvent: scFinishedEv{},
  1680  		},
  1681  		{
  1682  			name: "failed block, we still have blocks to process",
  1683  			fields: scTestParams{
  1684  				initHeight: 4,
  1685  				peers:      map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}},
  1686  				allB:       []int64{5, 6, 7, 8},
  1687  				pending:    map[int64]p2p.ID{7: "P1", 8: "P1"},
  1688  				received:   map[int64]p2p.ID{5: "P1", 6: "P1"},
  1689  			},
  1690  			args:      args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P1"}},
  1691  			wantEvent: noOpEvent{},
  1692  		},
  1693  		{
  1694  			name: "failed block, H+1 and H+2 delivered by different peers, we still have blocks to process",
  1695  			fields: scTestParams{
  1696  				initHeight: 4,
  1697  				peers: map[string]*scPeer{
  1698  					"P1": {height: 8, state: peerStateReady},
  1699  					"P2": {height: 8, state: peerStateReady},
  1700  					"P3": {height: 8, state: peerStateReady},
  1701  				},
  1702  				allB:     []int64{5, 6, 7, 8},
  1703  				pending:  map[int64]p2p.ID{7: "P1", 8: "P1"},
  1704  				received: map[int64]p2p.ID{5: "P1", 6: "P1"},
  1705  			},
  1706  			args:      args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P2"}},
  1707  			wantEvent: noOpEvent{},
  1708  		},
  1709  	}
  1710  
  1711  	for _, tt := range tests {
  1712  		tt := tt
  1713  		t.Run(tt.name, func(t *testing.T) {
  1714  			sc := newTestScheduler(tt.fields)
  1715  			event, err := sc.handleBlockProcessError(tt.args.event)
  1716  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1717  		})
  1718  	}
  1719  }
  1720  
  1721  func TestScHandleAddNewPeer(t *testing.T) {
  1722  	addP1 := bcAddNewPeer{
  1723  		peerID: p2p.ID("P1"),
  1724  	}
  1725  	type args struct {
  1726  		event bcAddNewPeer
  1727  	}
  1728  
  1729  	tests := []struct {
  1730  		name      string
  1731  		fields    scTestParams
  1732  		args      args
  1733  		wantEvent Event
  1734  		wantErr   bool
  1735  	}{
  1736  		{
  1737  			name:      "add P1 to empty scheduler",
  1738  			fields:    scTestParams{},
  1739  			args:      args{event: addP1},
  1740  			wantEvent: noOpEvent{},
  1741  		},
  1742  		{
  1743  			name: "add duplicate peer",
  1744  			fields: scTestParams{
  1745  				height: 6,
  1746  				peers:  map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1747  				allB:   []int64{6, 7, 8},
  1748  			},
  1749  			args:      args{event: addP1},
  1750  			wantEvent: noOpEvent{},
  1751  		},
  1752  		{
  1753  			name: "add P1 to non empty scheduler",
  1754  			fields: scTestParams{
  1755  				height: 6,
  1756  				peers:  map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
  1757  				allB:   []int64{6, 7, 8},
  1758  			},
  1759  			args:      args{event: addP1},
  1760  			wantEvent: noOpEvent{},
  1761  		},
  1762  	}
  1763  
  1764  	for _, tt := range tests {
  1765  		tt := tt
  1766  		t.Run(tt.name, func(t *testing.T) {
  1767  			sc := newTestScheduler(tt.fields)
  1768  			event, err := sc.handleAddNewPeer(tt.args.event)
  1769  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1770  		})
  1771  	}
  1772  }
  1773  
  1774  func TestScHandleTryPrunePeer(t *testing.T) {
  1775  	now := time.Now()
  1776  
  1777  	pruneEv := rTryPrunePeer{
  1778  		time: now.Add(time.Second + time.Millisecond),
  1779  	}
  1780  	type args struct {
  1781  		event rTryPrunePeer
  1782  	}
  1783  
  1784  	tests := []struct {
  1785  		name      string
  1786  		fields    scTestParams
  1787  		args      args
  1788  		wantEvent Event
  1789  		wantErr   bool
  1790  	}{
  1791  		{
  1792  			name:      "no peers",
  1793  			fields:    scTestParams{},
  1794  			args:      args{event: pruneEv},
  1795  			wantEvent: noOpEvent{},
  1796  		},
  1797  		{
  1798  			name: "no prunable peers",
  1799  			fields: scTestParams{
  1800  				minRecvRate: 100,
  1801  				peers: map[string]*scPeer{
  1802  					// X - removed, active, fast
  1803  					"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101},
  1804  					// X - ready, active, fast
  1805  					"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101},
  1806  					// X - removed, active, equal
  1807  					"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100}},
  1808  				peerTimeout: time.Second,
  1809  			},
  1810  			args:      args{event: pruneEv},
  1811  			wantEvent: noOpEvent{},
  1812  		},
  1813  		{
  1814  			name: "mixed peers",
  1815  			fields: scTestParams{
  1816  				minRecvRate: 100,
  1817  				peers: map[string]*scPeer{
  1818  					// X - removed, active, fast
  1819  					"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
  1820  					// X - ready, active, fast
  1821  					"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
  1822  					// X - removed, active, equal
  1823  					"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5},
  1824  					// V - ready, inactive, equal
  1825  					"P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7},
  1826  					// V - ready, inactive, slow
  1827  					"P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7},
  1828  					//  V - ready, active, slow
  1829  					"P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7},
  1830  				},
  1831  				allB:        []int64{1, 2, 3, 4, 5, 6, 7},
  1832  				peerTimeout: time.Second},
  1833  			args:      args{event: pruneEv},
  1834  			wantEvent: scPeersPruned{peers: []p2p.ID{"P4", "P5", "P6"}},
  1835  		},
  1836  		{
  1837  			name: "mixed peers, finish after pruning",
  1838  			fields: scTestParams{
  1839  				minRecvRate: 100,
  1840  				height:      6,
  1841  				peers: map[string]*scPeer{
  1842  					// X - removed, active, fast
  1843  					"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
  1844  					// X - ready, active, fast
  1845  					"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
  1846  					// X - removed, active, equal
  1847  					"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5},
  1848  					// V - ready, inactive, equal
  1849  					"P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7},
  1850  					// V - ready, inactive, slow
  1851  					"P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7},
  1852  					//  V - ready, active, slow
  1853  					"P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7},
  1854  				},
  1855  				allB:        []int64{6, 7},
  1856  				peerTimeout: time.Second},
  1857  			args:      args{event: pruneEv},
  1858  			wantEvent: scFinishedEv{},
  1859  		},
  1860  	}
  1861  
  1862  	for _, tt := range tests {
  1863  		tt := tt
  1864  		t.Run(tt.name, func(t *testing.T) {
  1865  			sc := newTestScheduler(tt.fields)
  1866  			event, err := sc.handleTryPrunePeer(tt.args.event)
  1867  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1868  		})
  1869  	}
  1870  }
  1871  
  1872  func TestScHandleTrySchedule(t *testing.T) {
  1873  	now := time.Now()
  1874  	tryEv := rTrySchedule{
  1875  		time: now.Add(time.Second + time.Millisecond),
  1876  	}
  1877  
  1878  	type args struct {
  1879  		event rTrySchedule
  1880  	}
  1881  	tests := []struct {
  1882  		name      string
  1883  		fields    scTestParams
  1884  		args      args
  1885  		wantEvent Event
  1886  		wantErr   bool
  1887  	}{
  1888  		{
  1889  			name:      "no peers",
  1890  			fields:    scTestParams{startTime: now, peers: map[string]*scPeer{}},
  1891  			args:      args{event: tryEv},
  1892  			wantEvent: noOpEvent{},
  1893  		},
  1894  		{
  1895  			name:      "only new peers",
  1896  			fields:    scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
  1897  			args:      args{event: tryEv},
  1898  			wantEvent: noOpEvent{},
  1899  		},
  1900  		{
  1901  			name:      "only Removed peers",
  1902  			fields:    scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
  1903  			args:      args{event: tryEv},
  1904  			wantEvent: noOpEvent{},
  1905  		},
  1906  		{
  1907  			name: "one Ready shorter peer",
  1908  			fields: scTestParams{
  1909  				startTime: now,
  1910  				height:    6,
  1911  				peers:     map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}},
  1912  			args:      args{event: tryEv},
  1913  			wantEvent: noOpEvent{},
  1914  		},
  1915  		{
  1916  			name: "one Ready equal peer",
  1917  			fields: scTestParams{
  1918  				startTime: now,
  1919  				peers:     map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1920  				allB:      []int64{1, 2, 3, 4}},
  1921  			args:      args{event: tryEv},
  1922  			wantEvent: scBlockRequest{peerID: "P1", height: 1},
  1923  		},
  1924  		{
  1925  			name: "many Ready higher peers with different number of pending requests",
  1926  			fields: scTestParams{
  1927  				startTime: now,
  1928  				peers: map[string]*scPeer{
  1929  					"P1": {height: 4, state: peerStateReady},
  1930  					"P2": {height: 5, state: peerStateReady}},
  1931  				allB: []int64{1, 2, 3, 4, 5},
  1932  				pending: map[int64]p2p.ID{
  1933  					1: "P1", 2: "P1",
  1934  					3: "P2",
  1935  				},
  1936  			},
  1937  			args:      args{event: tryEv},
  1938  			wantEvent: scBlockRequest{peerID: "P2", height: 4},
  1939  		},
  1940  
  1941  		{
  1942  			name: "many Ready higher peers with same number of pending requests",
  1943  			fields: scTestParams{
  1944  				startTime: now,
  1945  				peers: map[string]*scPeer{
  1946  					"P2": {height: 8, state: peerStateReady},
  1947  					"P1": {height: 8, state: peerStateReady},
  1948  					"P3": {height: 8, state: peerStateReady}},
  1949  				allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1950  				pending: map[int64]p2p.ID{
  1951  					1: "P1", 2: "P1",
  1952  					3: "P3", 4: "P3",
  1953  					5: "P2", 6: "P2",
  1954  				},
  1955  			},
  1956  			args:      args{event: tryEv},
  1957  			wantEvent: scBlockRequest{peerID: "P1", height: 7},
  1958  		},
  1959  	}
  1960  
  1961  	for _, tt := range tests {
  1962  		tt := tt
  1963  		t.Run(tt.name, func(t *testing.T) {
  1964  			sc := newTestScheduler(tt.fields)
  1965  			event, err := sc.handleTrySchedule(tt.args.event)
  1966  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1967  		})
  1968  	}
  1969  }
  1970  
  1971  func TestScHandleStatusResponse(t *testing.T) {
  1972  	now := time.Now()
  1973  	statusRespP1Ev := bcStatusResponse{
  1974  		time:   now.Add(time.Second + time.Millisecond),
  1975  		peerID: "P1",
  1976  		height: 6,
  1977  	}
  1978  
  1979  	type args struct {
  1980  		event bcStatusResponse
  1981  	}
  1982  	tests := []struct {
  1983  		name      string
  1984  		fields    scTestParams
  1985  		args      args
  1986  		wantEvent Event
  1987  		wantErr   bool
  1988  	}{
  1989  		{
  1990  			name: "change height of non existing peer",
  1991  			fields: scTestParams{
  1992  				peers: map[string]*scPeer{"P2": {height: 2, state: peerStateReady}},
  1993  				allB:  []int64{1, 2},
  1994  			},
  1995  			args:      args{event: statusRespP1Ev},
  1996  			wantEvent: noOpEvent{},
  1997  		},
  1998  
  1999  		{
  2000  			name:      "increase height of removed peer",
  2001  			fields:    scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
  2002  			args:      args{event: statusRespP1Ev},
  2003  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  2004  		},
  2005  
  2006  		{
  2007  			name: "decrease height of single peer",
  2008  			fields: scTestParams{
  2009  				height: 5,
  2010  				peers:  map[string]*scPeer{"P1": {height: 10, state: peerStateReady}},
  2011  				allB:   []int64{5, 6, 7, 8, 9, 10},
  2012  			},
  2013  			args:      args{event: statusRespP1Ev},
  2014  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  2015  		},
  2016  
  2017  		{
  2018  			name: "increase height of single peer",
  2019  			fields: scTestParams{
  2020  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
  2021  				allB:  []int64{1, 2}},
  2022  			args:      args{event: statusRespP1Ev},
  2023  			wantEvent: noOpEvent{},
  2024  		},
  2025  		{
  2026  			name: "noop height change of single peer",
  2027  			fields: scTestParams{
  2028  				peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}},
  2029  				allB:  []int64{1, 2, 3, 4, 5, 6}},
  2030  			args:      args{event: statusRespP1Ev},
  2031  			wantEvent: noOpEvent{},
  2032  		},
  2033  	}
  2034  
  2035  	for _, tt := range tests {
  2036  		tt := tt
  2037  		t.Run(tt.name, func(t *testing.T) {
  2038  			sc := newTestScheduler(tt.fields)
  2039  			event, err := sc.handleStatusResponse(tt.args.event)
  2040  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  2041  		})
  2042  	}
  2043  }
  2044  
  2045  func TestScHandle(t *testing.T) {
  2046  	now := time.Now()
  2047  
  2048  	type unknownEv struct {
  2049  		priorityNormal
  2050  	}
  2051  
  2052  	t0 := time.Now()
  2053  	tick := make([]time.Time, 100)
  2054  	for i := range tick {
  2055  		tick[i] = t0.Add(time.Duration(i) * time.Millisecond)
  2056  	}
  2057  
  2058  	type args struct {
  2059  		event Event
  2060  	}
  2061  	type scStep struct {
  2062  		currentSc *scTestParams
  2063  		args      args
  2064  		wantEvent Event
  2065  		wantErr   bool
  2066  		wantSc    *scTestParams
  2067  	}
  2068  	tests := []struct {
  2069  		name  string
  2070  		steps []scStep
  2071  	}{
  2072  		{
  2073  			name: "unknown event",
  2074  			steps: []scStep{
  2075  				{ // add P1
  2076  					currentSc: &scTestParams{},
  2077  					args:      args{event: unknownEv{}},
  2078  					wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")},
  2079  					wantSc:    &scTestParams{},
  2080  				},
  2081  			},
  2082  		},
  2083  		{
  2084  			name: "single peer, sync 3 blocks",
  2085  			steps: []scStep{
  2086  				{ // add P1
  2087  					currentSc: &scTestParams{startTime: now, peers: map[string]*scPeer{}, height: 1},
  2088  					args:      args{event: bcAddNewPeer{peerID: "P1"}},
  2089  					wantEvent: noOpEvent{},
  2090  					wantSc: &scTestParams{startTime: now, peers: map[string]*scPeer{
  2091  						"P1": {base: -1, height: -1, state: peerStateNew}}, height: 1},
  2092  				},
  2093  				{ // set height of P1
  2094  					args:      args{event: bcStatusResponse{peerID: "P1", time: tick[0], height: 3}},
  2095  					wantEvent: noOpEvent{},
  2096  					wantSc: &scTestParams{
  2097  						startTime: now,
  2098  						peers:     map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
  2099  						allB:      []int64{1, 2, 3},
  2100  						height:    1,
  2101  					},
  2102  				},
  2103  				{ // schedule block 1
  2104  					args:      args{event: rTrySchedule{time: tick[1]}},
  2105  					wantEvent: scBlockRequest{peerID: "P1", height: 1},
  2106  					wantSc: &scTestParams{
  2107  						startTime:   now,
  2108  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
  2109  						allB:        []int64{1, 2, 3},
  2110  						pending:     map[int64]p2p.ID{1: "P1"},
  2111  						pendingTime: map[int64]time.Time{1: tick[1]},
  2112  						height:      1,
  2113  					},
  2114  				},
  2115  				{ // schedule block 2
  2116  					args:      args{event: rTrySchedule{time: tick[2]}},
  2117  					wantEvent: scBlockRequest{peerID: "P1", height: 2},
  2118  					wantSc: &scTestParams{
  2119  						startTime:   now,
  2120  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
  2121  						allB:        []int64{1, 2, 3},
  2122  						pending:     map[int64]p2p.ID{1: "P1", 2: "P1"},
  2123  						pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2]},
  2124  						height:      1,
  2125  					},
  2126  				},
  2127  				{ // schedule block 3
  2128  					args:      args{event: rTrySchedule{time: tick[3]}},
  2129  					wantEvent: scBlockRequest{peerID: "P1", height: 3},
  2130  					wantSc: &scTestParams{
  2131  						startTime:   now,
  2132  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
  2133  						allB:        []int64{1, 2, 3},
  2134  						pending:     map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"},
  2135  						pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2], 3: tick[3]},
  2136  						height:      1,
  2137  					},
  2138  				},
  2139  				{ // block response 1
  2140  					args:      args{event: bcBlockResponse{peerID: "P1", time: tick[4], size: 100, block: makeScBlock(1)}},
  2141  					wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(1)},
  2142  					wantSc: &scTestParams{
  2143  						startTime:   now,
  2144  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[4]}},
  2145  						allB:        []int64{1, 2, 3},
  2146  						pending:     map[int64]p2p.ID{2: "P1", 3: "P1"},
  2147  						pendingTime: map[int64]time.Time{2: tick[2], 3: tick[3]},
  2148  						received:    map[int64]p2p.ID{1: "P1"},
  2149  						height:      1,
  2150  					},
  2151  				},
  2152  				{ // block response 2
  2153  					args:      args{event: bcBlockResponse{peerID: "P1", time: tick[5], size: 100, block: makeScBlock(2)}},
  2154  					wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(2)},
  2155  					wantSc: &scTestParams{
  2156  						startTime:   now,
  2157  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[5]}},
  2158  						allB:        []int64{1, 2, 3},
  2159  						pending:     map[int64]p2p.ID{3: "P1"},
  2160  						pendingTime: map[int64]time.Time{3: tick[3]},
  2161  						received:    map[int64]p2p.ID{1: "P1", 2: "P1"},
  2162  						height:      1,
  2163  					},
  2164  				},
  2165  				{ // block response 3
  2166  					args:      args{event: bcBlockResponse{peerID: "P1", time: tick[6], size: 100, block: makeScBlock(3)}},
  2167  					wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(3)},
  2168  					wantSc: &scTestParams{
  2169  						startTime: now,
  2170  						peers:     map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2171  						allB:      []int64{1, 2, 3},
  2172  						received:  map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"},
  2173  						height:    1,
  2174  					},
  2175  				},
  2176  				{ // processed block 1
  2177  					args:      args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 1}},
  2178  					wantEvent: noOpEvent{},
  2179  					wantSc: &scTestParams{
  2180  						startTime: now,
  2181  						peers:     map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2182  						allB:      []int64{2, 3},
  2183  						received:  map[int64]p2p.ID{2: "P1", 3: "P1"},
  2184  						height:    2,
  2185  					},
  2186  				},
  2187  				{ // processed block 2
  2188  					args:      args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 2}},
  2189  					wantEvent: scFinishedEv{},
  2190  					wantSc: &scTestParams{
  2191  						startTime: now,
  2192  						peers:     map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2193  						allB:      []int64{3},
  2194  						received:  map[int64]p2p.ID{3: "P1"},
  2195  						height:    3,
  2196  					},
  2197  				},
  2198  			},
  2199  		},
  2200  		{
  2201  			name: "block verification failure",
  2202  			steps: []scStep{
  2203  				{ // failure processing block 1
  2204  					currentSc: &scTestParams{
  2205  						startTime: now,
  2206  						peers: map[string]*scPeer{
  2207  							"P1": {height: 4, state: peerStateReady, lastTouched: tick[6]},
  2208  							"P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2209  						allB:     []int64{1, 2, 3, 4},
  2210  						received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"},
  2211  						height:   1,
  2212  					},
  2213  					args:      args{event: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P1"}},
  2214  					wantEvent: noOpEvent{},
  2215  					wantSc: &scTestParams{
  2216  						startTime: now,
  2217  						peers: map[string]*scPeer{
  2218  							"P1": {height: 4, state: peerStateRemoved, lastTouched: tick[6]},
  2219  							"P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2220  						allB:     []int64{1, 2, 3},
  2221  						received: map[int64]p2p.ID{},
  2222  						height:   1,
  2223  					},
  2224  				},
  2225  			},
  2226  		},
  2227  	}
  2228  
  2229  	for _, tt := range tests {
  2230  		tt := tt
  2231  		t.Run(tt.name, func(t *testing.T) {
  2232  			var sc *scheduler
  2233  			for i, step := range tt.steps {
  2234  				// First step must always initialise the currentState as state.
  2235  				if step.currentSc != nil {
  2236  					sc = newTestScheduler(*step.currentSc)
  2237  				}
  2238  				if sc == nil {
  2239  					panic("Bad (initial?) step")
  2240  				}
  2241  
  2242  				nextEvent, err := sc.handle(step.args.event)
  2243  				wantSc := newTestScheduler(*step.wantSc)
  2244  
  2245  				t.Logf("step %d(%v): %s", i, step.args.event, sc)
  2246  				checkSameScheduler(t, wantSc, sc)
  2247  
  2248  				checkScResults(t, step.wantErr, err, step.wantEvent, nextEvent)
  2249  
  2250  				// Next step may use the wantedState as their currentState.
  2251  				sc = newTestScheduler(*step.wantSc)
  2252  			}
  2253  		})
  2254  	}
  2255  }