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