github.com/okex/exchain@v1.8.0/libs/tendermint/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/okex/exchain/libs/tendermint/p2p"
    13  	"github.com/okex/exchain/libs/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  				height:        2,
   995  				peers:         map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
   996  				allB:          []int64{2},
   997  				pending:       map[int64]p2p.ID{2: "P1"},
   998  				pendingTime:   map[int64]time.Time{2: now},
   999  				targetPending: 1,
  1000  			},
  1001  			args: args{height: 2},
  1002  			wantFields: scTestParams{
  1003  				height:        3,
  1004  				peers:         map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1005  				allB:          []int64{3},
  1006  				targetPending: 1,
  1007  			},
  1008  		},
  1009  		{
  1010  			name: "mark processed success",
  1011  			fields: scTestParams{
  1012  				height:      1,
  1013  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
  1014  				allB:        []int64{1, 2},
  1015  				pending:     map[int64]p2p.ID{2: "P1"},
  1016  				pendingTime: map[int64]time.Time{2: now},
  1017  				received:    map[int64]p2p.ID{1: "P1"}},
  1018  			args: args{height: 1},
  1019  			wantFields: scTestParams{
  1020  				height:      2,
  1021  				peers:       map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
  1022  				allB:        []int64{2},
  1023  				pending:     map[int64]p2p.ID{2: "P1"},
  1024  				pendingTime: map[int64]time.Time{2: now}},
  1025  		},
  1026  	}
  1027  
  1028  	for _, tt := range tests {
  1029  		tt := tt
  1030  		t.Run(tt.name, func(t *testing.T) {
  1031  			sc := newTestScheduler(tt.fields)
  1032  			oldBlockState := sc.getStateAtHeight(tt.args.height)
  1033  			if err := sc.markProcessed(tt.args.height); (err != nil) != tt.wantErr {
  1034  				t.Errorf("markProcessed() wantErr %v, error = %v", tt.wantErr, err)
  1035  			}
  1036  			if tt.wantErr {
  1037  				assert.Equal(t, oldBlockState, sc.getStateAtHeight(tt.args.height))
  1038  			} else {
  1039  				assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(tt.args.height))
  1040  			}
  1041  			wantSc := newTestScheduler(tt.wantFields)
  1042  			checkSameScheduler(t, wantSc, sc)
  1043  		})
  1044  	}
  1045  }
  1046  
  1047  func TestScAllBlocksProcessed(t *testing.T) {
  1048  	now := time.Now()
  1049  
  1050  	tests := []struct {
  1051  		name       string
  1052  		fields     scTestParams
  1053  		wantResult bool
  1054  	}{
  1055  		{
  1056  			name:       "no blocks, no peers",
  1057  			fields:     scTestParams{},
  1058  			wantResult: false,
  1059  		},
  1060  		{
  1061  			name: "only New blocks",
  1062  			fields: scTestParams{
  1063  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1064  				allB:  []int64{1, 2, 3, 4},
  1065  			},
  1066  			wantResult: false,
  1067  		},
  1068  		{
  1069  			name: "only Pending blocks",
  1070  			fields: scTestParams{
  1071  				peers:       map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1072  				allB:        []int64{1, 2, 3, 4},
  1073  				pending:     map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
  1074  				pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now},
  1075  			},
  1076  			wantResult: false,
  1077  		},
  1078  		{
  1079  			name: "only Received blocks",
  1080  			fields: scTestParams{
  1081  				peers:    map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1082  				allB:     []int64{1, 2, 3, 4},
  1083  				received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
  1084  			},
  1085  			wantResult: false,
  1086  		},
  1087  		{
  1088  			name: "only Processed blocks plus highest is received",
  1089  			fields: scTestParams{
  1090  				height: 4,
  1091  				peers: map[string]*scPeer{
  1092  					"P1": {height: 4, state: peerStateReady}},
  1093  				allB:     []int64{4},
  1094  				received: map[int64]p2p.ID{4: "P1"},
  1095  			},
  1096  			wantResult: true,
  1097  		},
  1098  		{
  1099  			name: "mixed block states",
  1100  			fields: scTestParams{
  1101  				peers:       map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1102  				allB:        []int64{1, 2, 3, 4},
  1103  				pending:     map[int64]p2p.ID{2: "P1", 4: "P1"},
  1104  				pendingTime: map[int64]time.Time{2: now, 4: now},
  1105  			},
  1106  			wantResult: false,
  1107  		},
  1108  	}
  1109  
  1110  	for _, tt := range tests {
  1111  		tt := tt
  1112  		t.Run(tt.name, func(t *testing.T) {
  1113  			sc := newTestScheduler(tt.fields)
  1114  			// allBlocksProcessed() should not mutate the scheduler
  1115  			wantSc := sc
  1116  			res := sc.allBlocksProcessed()
  1117  			assert.Equal(t, tt.wantResult, res)
  1118  			checkSameScheduler(t, wantSc, sc)
  1119  		})
  1120  	}
  1121  }
  1122  
  1123  func TestScNextHeightToSchedule(t *testing.T) {
  1124  	now := time.Now()
  1125  
  1126  	tests := []struct {
  1127  		name       string
  1128  		fields     scTestParams
  1129  		wantHeight int64
  1130  	}{
  1131  		{
  1132  			name:       "no blocks",
  1133  			fields:     scTestParams{initHeight: 10, height: 11},
  1134  			wantHeight: -1,
  1135  		},
  1136  		{
  1137  			name: "only New blocks",
  1138  			fields: scTestParams{
  1139  				initHeight: 2,
  1140  				height:     3,
  1141  				peers:      map[string]*scPeer{"P1": {height: 6, state: peerStateReady}},
  1142  				allB:       []int64{3, 4, 5, 6},
  1143  			},
  1144  			wantHeight: 3,
  1145  		},
  1146  		{
  1147  			name: "only Pending blocks",
  1148  			fields: scTestParams{
  1149  				height:      1,
  1150  				peers:       map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1151  				allB:        []int64{1, 2, 3, 4},
  1152  				pending:     map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
  1153  				pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now},
  1154  			},
  1155  			wantHeight: -1,
  1156  		},
  1157  		{
  1158  			name: "only Received blocks",
  1159  			fields: scTestParams{
  1160  				height:   1,
  1161  				peers:    map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1162  				allB:     []int64{1, 2, 3, 4},
  1163  				received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
  1164  			},
  1165  			wantHeight: -1,
  1166  		},
  1167  		{
  1168  			name: "only Processed blocks",
  1169  			fields: scTestParams{
  1170  				height: 1,
  1171  				peers:  map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1172  				allB:   []int64{1, 2, 3, 4},
  1173  			},
  1174  			wantHeight: 1,
  1175  		},
  1176  		{
  1177  			name: "mixed block states",
  1178  			fields: scTestParams{
  1179  				height:      1,
  1180  				peers:       map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1181  				allB:        []int64{1, 2, 3, 4},
  1182  				pending:     map[int64]p2p.ID{2: "P1"},
  1183  				pendingTime: map[int64]time.Time{2: now},
  1184  			},
  1185  			wantHeight: 1,
  1186  		},
  1187  	}
  1188  
  1189  	for _, tt := range tests {
  1190  		tt := tt
  1191  		t.Run(tt.name, func(t *testing.T) {
  1192  			sc := newTestScheduler(tt.fields)
  1193  			// nextHeightToSchedule() should not mutate the scheduler
  1194  			wantSc := sc
  1195  
  1196  			resMin := sc.nextHeightToSchedule()
  1197  			assert.Equal(t, tt.wantHeight, resMin)
  1198  			checkSameScheduler(t, wantSc, sc)
  1199  		})
  1200  	}
  1201  }
  1202  
  1203  func TestScSelectPeer(t *testing.T) {
  1204  
  1205  	type args struct {
  1206  		height int64
  1207  	}
  1208  	tests := []struct {
  1209  		name       string
  1210  		fields     scTestParams
  1211  		args       args
  1212  		wantResult p2p.ID
  1213  		wantError  bool
  1214  	}{
  1215  		{
  1216  			name:       "no peers",
  1217  			fields:     scTestParams{peers: map[string]*scPeer{}},
  1218  			args:       args{height: 10},
  1219  			wantResult: "",
  1220  			wantError:  true,
  1221  		},
  1222  		{
  1223  			name:       "only new peers",
  1224  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
  1225  			args:       args{height: 10},
  1226  			wantResult: "",
  1227  			wantError:  true,
  1228  		},
  1229  		{
  1230  			name:       "only Removed peers",
  1231  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
  1232  			args:       args{height: 2},
  1233  			wantResult: "",
  1234  			wantError:  true,
  1235  		},
  1236  		{
  1237  			name: "one Ready shorter peer",
  1238  			fields: scTestParams{
  1239  				peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1240  				allB:  []int64{1, 2, 3, 4},
  1241  			},
  1242  			args:       args{height: 5},
  1243  			wantResult: "",
  1244  			wantError:  true,
  1245  		},
  1246  		{
  1247  			name: "one Ready equal peer",
  1248  			fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1249  				allB: []int64{1, 2, 3, 4},
  1250  			},
  1251  			args:       args{height: 4},
  1252  			wantResult: "P1",
  1253  		},
  1254  		{
  1255  			name: "one Ready higher peer",
  1256  			fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}},
  1257  				allB: []int64{1, 2, 3, 4, 5, 6},
  1258  			},
  1259  			args:       args{height: 4},
  1260  			wantResult: "P1",
  1261  		},
  1262  		{
  1263  			name: "one Ready higher peer with higher base",
  1264  			fields: scTestParams{
  1265  				peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
  1266  				allB:  []int64{1, 2, 3, 4, 5, 6},
  1267  			},
  1268  			args:       args{height: 3},
  1269  			wantResult: "",
  1270  			wantError:  true,
  1271  		},
  1272  		{
  1273  			name: "many Ready higher peers with different number of pending requests",
  1274  			fields: scTestParams{
  1275  				height: 4,
  1276  				peers: map[string]*scPeer{
  1277  					"P1": {height: 8, state: peerStateReady},
  1278  					"P2": {height: 9, state: peerStateReady}},
  1279  				allB: []int64{4, 5, 6, 7, 8, 9},
  1280  				pending: map[int64]p2p.ID{
  1281  					4: "P1", 6: "P1",
  1282  					5: "P2",
  1283  				},
  1284  			},
  1285  			args:       args{height: 4},
  1286  			wantResult: "P2",
  1287  		},
  1288  		{
  1289  			name: "many Ready higher peers with same number of pending requests",
  1290  			fields: scTestParams{
  1291  				peers: map[string]*scPeer{
  1292  					"P2": {height: 20, state: peerStateReady},
  1293  					"P1": {height: 15, state: peerStateReady},
  1294  					"P3": {height: 15, state: peerStateReady}},
  1295  				allB: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
  1296  				pending: map[int64]p2p.ID{
  1297  					1: "P1", 2: "P1",
  1298  					3: "P3", 4: "P3",
  1299  					5: "P2", 6: "P2",
  1300  				},
  1301  			},
  1302  			args:       args{height: 7},
  1303  			wantResult: "P1",
  1304  		},
  1305  	}
  1306  
  1307  	for _, tt := range tests {
  1308  		tt := tt
  1309  		t.Run(tt.name, func(t *testing.T) {
  1310  			sc := newTestScheduler(tt.fields)
  1311  			// selectPeer should not mutate the scheduler
  1312  			wantSc := sc
  1313  			res, err := sc.selectPeer(tt.args.height)
  1314  			assert.Equal(t, tt.wantResult, res)
  1315  			assert.Equal(t, tt.wantError, err != nil)
  1316  			checkSameScheduler(t, wantSc, sc)
  1317  		})
  1318  	}
  1319  }
  1320  
  1321  // makeScBlock makes an empty block.
  1322  func makeScBlock(height int64) *types.Block {
  1323  	return &types.Block{Header: types.Header{Height: height}}
  1324  }
  1325  
  1326  // used in place of assert.Equal(t, want, actual) to avoid failures due to
  1327  // scheduler.lastAdvanced timestamp inequalities.
  1328  func checkSameScheduler(t *testing.T, want *scheduler, actual *scheduler) {
  1329  	assert.Equal(t, want.initHeight, actual.initHeight)
  1330  	assert.Equal(t, want.height, actual.height)
  1331  	assert.Equal(t, want.peers, actual.peers)
  1332  	assert.Equal(t, want.blockStates, actual.blockStates)
  1333  	assert.Equal(t, want.pendingBlocks, actual.pendingBlocks)
  1334  	assert.Equal(t, want.pendingTime, actual.pendingTime)
  1335  	assert.Equal(t, want.blockStates, actual.blockStates)
  1336  	assert.Equal(t, want.receivedBlocks, actual.receivedBlocks)
  1337  	assert.Equal(t, want.blockStates, actual.blockStates)
  1338  }
  1339  
  1340  // checkScResults checks scheduler handler test results
  1341  func checkScResults(t *testing.T, wantErr bool, err error, wantEvent Event, event Event) {
  1342  	if (err != nil) != wantErr {
  1343  		t.Errorf("error = %v, wantErr %v", err, wantErr)
  1344  		return
  1345  	}
  1346  	switch wantEvent := wantEvent.(type) {
  1347  	case scPeerError:
  1348  		assert.Equal(t, wantEvent.peerID, event.(scPeerError).peerID)
  1349  		assert.Equal(t, wantEvent.reason != nil, event.(scPeerError).reason != nil)
  1350  	case scBlockReceived:
  1351  		assert.Equal(t, wantEvent.peerID, event.(scBlockReceived).peerID)
  1352  		assert.Equal(t, wantEvent.block, event.(scBlockReceived).block)
  1353  	case scSchedulerFail:
  1354  		assert.Equal(t, wantEvent.reason != nil, event.(scSchedulerFail).reason != nil)
  1355  	}
  1356  }
  1357  
  1358  func TestScHandleBlockResponse(t *testing.T) {
  1359  	now := time.Now()
  1360  	block6FromP1 := bcBlockResponse{
  1361  		time:   now.Add(time.Millisecond),
  1362  		peerID: p2p.ID("P1"),
  1363  		size:   100,
  1364  		block:  makeScBlock(6),
  1365  	}
  1366  
  1367  	type args struct {
  1368  		event bcBlockResponse
  1369  	}
  1370  
  1371  	tests := []struct {
  1372  		name      string
  1373  		fields    scTestParams
  1374  		args      args
  1375  		wantEvent Event
  1376  		wantErr   bool
  1377  	}{
  1378  		{
  1379  			name:      "empty scheduler",
  1380  			fields:    scTestParams{},
  1381  			args:      args{event: block6FromP1},
  1382  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1383  		},
  1384  		{
  1385  			name:      "block from removed peer",
  1386  			fields:    scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1387  			args:      args{event: block6FromP1},
  1388  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1389  		},
  1390  		{
  1391  			name: "block we haven't asked for",
  1392  			fields: scTestParams{
  1393  				peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1394  				allB:  []int64{1, 2, 3, 4, 5, 6, 7, 8}},
  1395  			args:      args{event: block6FromP1},
  1396  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1397  		},
  1398  		{
  1399  			name: "block from wrong peer",
  1400  			fields: scTestParams{
  1401  				peers:       map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
  1402  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1403  				pending:     map[int64]p2p.ID{6: "P2"},
  1404  				pendingTime: map[int64]time.Time{6: now},
  1405  			},
  1406  			args:      args{event: block6FromP1},
  1407  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1408  		},
  1409  		{
  1410  			name: "block with bad timestamp",
  1411  			fields: scTestParams{
  1412  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1413  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1414  				pending:     map[int64]p2p.ID{6: "P1"},
  1415  				pendingTime: map[int64]time.Time{6: now.Add(time.Second)},
  1416  			},
  1417  			args:      args{event: block6FromP1},
  1418  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1419  		},
  1420  		{
  1421  			name: "good block, accept",
  1422  			fields: scTestParams{
  1423  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1424  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1425  				pending:     map[int64]p2p.ID{6: "P1"},
  1426  				pendingTime: map[int64]time.Time{6: now},
  1427  			},
  1428  			args:      args{event: block6FromP1},
  1429  			wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(6)},
  1430  		},
  1431  	}
  1432  
  1433  	for _, tt := range tests {
  1434  		tt := tt
  1435  		t.Run(tt.name, func(t *testing.T) {
  1436  			sc := newTestScheduler(tt.fields)
  1437  			event, err := sc.handleBlockResponse(tt.args.event)
  1438  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1439  		})
  1440  	}
  1441  }
  1442  
  1443  func TestScHandleNoBlockResponse(t *testing.T) {
  1444  	now := time.Now()
  1445  	noBlock6FromP1 := bcNoBlockResponse{
  1446  		time:   now.Add(time.Millisecond),
  1447  		peerID: p2p.ID("P1"),
  1448  		height: 6,
  1449  	}
  1450  
  1451  	tests := []struct {
  1452  		name       string
  1453  		fields     scTestParams
  1454  		wantEvent  Event
  1455  		wantFields scTestParams
  1456  		wantErr    bool
  1457  	}{
  1458  		{
  1459  			name:       "empty scheduler",
  1460  			fields:     scTestParams{},
  1461  			wantEvent:  noOpEvent{},
  1462  			wantFields: scTestParams{},
  1463  		},
  1464  		{
  1465  			name:       "noBlock from removed peer",
  1466  			fields:     scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1467  			wantEvent:  noOpEvent{},
  1468  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1469  		},
  1470  		{
  1471  			name: "for block we haven't asked for",
  1472  			fields: scTestParams{
  1473  				peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1474  				allB:  []int64{1, 2, 3, 4, 5, 6, 7, 8}},
  1475  			wantEvent:  scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1476  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1477  		},
  1478  		{
  1479  			name: "noBlock from peer we don't have",
  1480  			fields: scTestParams{
  1481  				peers:       map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
  1482  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1483  				pending:     map[int64]p2p.ID{6: "P2"},
  1484  				pendingTime: map[int64]time.Time{6: now},
  1485  			},
  1486  			wantEvent: noOpEvent{},
  1487  			wantFields: scTestParams{
  1488  				peers:       map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
  1489  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1490  				pending:     map[int64]p2p.ID{6: "P2"},
  1491  				pendingTime: map[int64]time.Time{6: now},
  1492  			},
  1493  		},
  1494  		{
  1495  			name: "noBlock from existing peer",
  1496  			fields: scTestParams{
  1497  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1498  				allB:        []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1499  				pending:     map[int64]p2p.ID{6: "P1"},
  1500  				pendingTime: map[int64]time.Time{6: now},
  1501  			},
  1502  			wantEvent:  scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1503  			wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
  1504  		},
  1505  	}
  1506  
  1507  	for _, tt := range tests {
  1508  		tt := tt
  1509  		t.Run(tt.name, func(t *testing.T) {
  1510  			sc := newTestScheduler(tt.fields)
  1511  			event, err := sc.handleNoBlockResponse(noBlock6FromP1)
  1512  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1513  			wantSc := newTestScheduler(tt.wantFields)
  1514  			assert.Equal(t, wantSc, sc)
  1515  		})
  1516  	}
  1517  }
  1518  
  1519  func TestScHandleBlockProcessed(t *testing.T) {
  1520  	now := time.Now()
  1521  	processed6FromP1 := pcBlockProcessed{
  1522  		peerID: p2p.ID("P1"),
  1523  		height: 6,
  1524  	}
  1525  
  1526  	type args struct {
  1527  		event pcBlockProcessed
  1528  	}
  1529  
  1530  	tests := []struct {
  1531  		name      string
  1532  		fields    scTestParams
  1533  		args      args
  1534  		wantEvent Event
  1535  		wantErr   bool
  1536  	}{
  1537  		{
  1538  			name:      "empty scheduler",
  1539  			fields:    scTestParams{height: 6},
  1540  			args:      args{event: processed6FromP1},
  1541  			wantEvent: noOpEvent{},
  1542  		},
  1543  		{
  1544  			name: "processed block we don't have",
  1545  			fields: scTestParams{
  1546  				initHeight:  5,
  1547  				height:      6,
  1548  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1549  				allB:        []int64{6, 7, 8},
  1550  				pending:     map[int64]p2p.ID{6: "P1"},
  1551  				pendingTime: map[int64]time.Time{6: now},
  1552  			},
  1553  			args:      args{event: processed6FromP1},
  1554  			wantEvent: noOpEvent{},
  1555  		},
  1556  		{
  1557  			name: "processed block ok, we processed all blocks",
  1558  			fields: scTestParams{
  1559  				initHeight: 5,
  1560  				height:     6,
  1561  				peers:      map[string]*scPeer{"P1": {height: 7, state: peerStateReady}},
  1562  				allB:       []int64{6, 7},
  1563  				received:   map[int64]p2p.ID{6: "P1", 7: "P1"},
  1564  			},
  1565  			args:      args{event: processed6FromP1},
  1566  			wantEvent: scFinishedEv{},
  1567  		},
  1568  		{
  1569  			name: "processed block ok, we still have blocks to process",
  1570  			fields: scTestParams{
  1571  				initHeight: 5,
  1572  				height:     6,
  1573  				peers:      map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1574  				allB:       []int64{6, 7, 8},
  1575  				pending:    map[int64]p2p.ID{7: "P1", 8: "P1"},
  1576  				received:   map[int64]p2p.ID{6: "P1"},
  1577  			},
  1578  			args:      args{event: processed6FromP1},
  1579  			wantEvent: noOpEvent{},
  1580  		},
  1581  	}
  1582  
  1583  	for _, tt := range tests {
  1584  		tt := tt
  1585  		t.Run(tt.name, func(t *testing.T) {
  1586  			sc := newTestScheduler(tt.fields)
  1587  			event, err := sc.handleBlockProcessed(tt.args.event)
  1588  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1589  		})
  1590  	}
  1591  }
  1592  
  1593  func TestScHandleBlockVerificationFailure(t *testing.T) {
  1594  	now := time.Now()
  1595  
  1596  	type args struct {
  1597  		event pcBlockVerificationFailure
  1598  	}
  1599  
  1600  	tests := []struct {
  1601  		name      string
  1602  		fields    scTestParams
  1603  		args      args
  1604  		wantEvent Event
  1605  		wantErr   bool
  1606  	}{
  1607  		{
  1608  			name:      "empty scheduler",
  1609  			fields:    scTestParams{},
  1610  			args:      args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}},
  1611  			wantEvent: noOpEvent{},
  1612  		},
  1613  		{
  1614  			name: "failed block we don't have, single peer is still removed",
  1615  			fields: scTestParams{
  1616  				initHeight:  5,
  1617  				height:      6,
  1618  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1619  				allB:        []int64{6, 7, 8},
  1620  				pending:     map[int64]p2p.ID{6: "P1"},
  1621  				pendingTime: map[int64]time.Time{6: now},
  1622  			},
  1623  			args:      args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}},
  1624  			wantEvent: scFinishedEv{},
  1625  		},
  1626  		{
  1627  			name: "failed block we don't have, one of two peers are removed",
  1628  			fields: scTestParams{
  1629  				initHeight:  5,
  1630  				peers:       map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}},
  1631  				allB:        []int64{6, 7, 8},
  1632  				pending:     map[int64]p2p.ID{6: "P1"},
  1633  				pendingTime: map[int64]time.Time{6: now},
  1634  			},
  1635  			args:      args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}},
  1636  			wantEvent: noOpEvent{},
  1637  		},
  1638  		{
  1639  			name: "failed block, all blocks are processed after removal",
  1640  			fields: scTestParams{
  1641  				initHeight: 5,
  1642  				height:     6,
  1643  				peers:      map[string]*scPeer{"P1": {height: 7, state: peerStateReady}},
  1644  				allB:       []int64{6, 7},
  1645  				received:   map[int64]p2p.ID{6: "P1", 7: "P1"},
  1646  			},
  1647  			args:      args{event: pcBlockVerificationFailure{height: 7, firstPeerID: "P1", secondPeerID: "P1"}},
  1648  			wantEvent: scFinishedEv{},
  1649  		},
  1650  		{
  1651  			name: "failed block, we still have blocks to process",
  1652  			fields: scTestParams{
  1653  				initHeight: 4,
  1654  				peers:      map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}},
  1655  				allB:       []int64{5, 6, 7, 8},
  1656  				pending:    map[int64]p2p.ID{7: "P1", 8: "P1"},
  1657  				received:   map[int64]p2p.ID{5: "P1", 6: "P1"},
  1658  			},
  1659  			args:      args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P1"}},
  1660  			wantEvent: noOpEvent{},
  1661  		},
  1662  		{
  1663  			name: "failed block, H+1 and H+2 delivered by different peers, we still have blocks to process",
  1664  			fields: scTestParams{
  1665  				initHeight: 4,
  1666  				peers: map[string]*scPeer{
  1667  					"P1": {height: 8, state: peerStateReady},
  1668  					"P2": {height: 8, state: peerStateReady},
  1669  					"P3": {height: 8, state: peerStateReady},
  1670  				},
  1671  				allB:     []int64{5, 6, 7, 8},
  1672  				pending:  map[int64]p2p.ID{7: "P1", 8: "P1"},
  1673  				received: map[int64]p2p.ID{5: "P1", 6: "P1"},
  1674  			},
  1675  			args:      args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P2"}},
  1676  			wantEvent: noOpEvent{},
  1677  		},
  1678  	}
  1679  
  1680  	for _, tt := range tests {
  1681  		tt := tt
  1682  		t.Run(tt.name, func(t *testing.T) {
  1683  			sc := newTestScheduler(tt.fields)
  1684  			event, err := sc.handleBlockProcessError(tt.args.event)
  1685  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1686  		})
  1687  	}
  1688  }
  1689  
  1690  func TestScHandleAddNewPeer(t *testing.T) {
  1691  	addP1 := bcAddNewPeer{
  1692  		peerID: p2p.ID("P1"),
  1693  	}
  1694  	type args struct {
  1695  		event bcAddNewPeer
  1696  	}
  1697  
  1698  	tests := []struct {
  1699  		name      string
  1700  		fields    scTestParams
  1701  		args      args
  1702  		wantEvent Event
  1703  		wantErr   bool
  1704  	}{
  1705  		{
  1706  			name:      "add P1 to empty scheduler",
  1707  			fields:    scTestParams{},
  1708  			args:      args{event: addP1},
  1709  			wantEvent: noOpEvent{},
  1710  		},
  1711  		{
  1712  			name: "add duplicate peer",
  1713  			fields: scTestParams{
  1714  				height: 6,
  1715  				peers:  map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
  1716  				allB:   []int64{6, 7, 8},
  1717  			},
  1718  			args:      args{event: addP1},
  1719  			wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")},
  1720  		},
  1721  		{
  1722  			name: "add P1 to non empty scheduler",
  1723  			fields: scTestParams{
  1724  				height: 6,
  1725  				peers:  map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
  1726  				allB:   []int64{6, 7, 8},
  1727  			},
  1728  			args:      args{event: addP1},
  1729  			wantEvent: noOpEvent{},
  1730  		},
  1731  	}
  1732  
  1733  	for _, tt := range tests {
  1734  		tt := tt
  1735  		t.Run(tt.name, func(t *testing.T) {
  1736  			sc := newTestScheduler(tt.fields)
  1737  			event, err := sc.handleAddNewPeer(tt.args.event)
  1738  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1739  		})
  1740  	}
  1741  }
  1742  
  1743  func TestScHandleTryPrunePeer(t *testing.T) {
  1744  	now := time.Now()
  1745  
  1746  	pruneEv := rTryPrunePeer{
  1747  		time: now.Add(time.Second + time.Millisecond),
  1748  	}
  1749  	type args struct {
  1750  		event rTryPrunePeer
  1751  	}
  1752  
  1753  	tests := []struct {
  1754  		name      string
  1755  		fields    scTestParams
  1756  		args      args
  1757  		wantEvent Event
  1758  		wantErr   bool
  1759  	}{
  1760  		{
  1761  			name:      "no peers",
  1762  			fields:    scTestParams{},
  1763  			args:      args{event: pruneEv},
  1764  			wantEvent: noOpEvent{},
  1765  		},
  1766  		{
  1767  			name: "no prunable peers",
  1768  			fields: scTestParams{
  1769  				minRecvRate: 100,
  1770  				peers: map[string]*scPeer{
  1771  					// X - removed, active, fast
  1772  					"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101},
  1773  					// X - ready, active, fast
  1774  					"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101},
  1775  					// X - removed, active, equal
  1776  					"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100}},
  1777  				peerTimeout: time.Second,
  1778  			},
  1779  			args:      args{event: pruneEv},
  1780  			wantEvent: noOpEvent{},
  1781  		},
  1782  		{
  1783  			name: "mixed peers",
  1784  			fields: scTestParams{
  1785  				minRecvRate: 100,
  1786  				peers: map[string]*scPeer{
  1787  					// X - removed, active, fast
  1788  					"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
  1789  					// X - ready, active, fast
  1790  					"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
  1791  					// X - removed, active, equal
  1792  					"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5},
  1793  					// V - ready, inactive, equal
  1794  					"P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7},
  1795  					// V - ready, inactive, slow
  1796  					"P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7},
  1797  					//  V - ready, active, slow
  1798  					"P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7},
  1799  				},
  1800  				allB:        []int64{1, 2, 3, 4, 5, 6, 7},
  1801  				peerTimeout: time.Second},
  1802  			args:      args{event: pruneEv},
  1803  			wantEvent: scPeersPruned{peers: []p2p.ID{"P4", "P5", "P6"}},
  1804  		},
  1805  		{
  1806  			name: "mixed peers, finish after pruning",
  1807  			fields: scTestParams{
  1808  				minRecvRate: 100,
  1809  				height:      6,
  1810  				peers: map[string]*scPeer{
  1811  					// X - removed, active, fast
  1812  					"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
  1813  					// X - ready, active, fast
  1814  					"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
  1815  					// X - removed, active, equal
  1816  					"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5},
  1817  					// V - ready, inactive, equal
  1818  					"P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7},
  1819  					// V - ready, inactive, slow
  1820  					"P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7},
  1821  					//  V - ready, active, slow
  1822  					"P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7},
  1823  				},
  1824  				allB:        []int64{6, 7},
  1825  				peerTimeout: time.Second},
  1826  			args:      args{event: pruneEv},
  1827  			wantEvent: scFinishedEv{},
  1828  		},
  1829  	}
  1830  
  1831  	for _, tt := range tests {
  1832  		tt := tt
  1833  		t.Run(tt.name, func(t *testing.T) {
  1834  			sc := newTestScheduler(tt.fields)
  1835  			event, err := sc.handleTryPrunePeer(tt.args.event)
  1836  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1837  		})
  1838  	}
  1839  }
  1840  
  1841  func TestScHandleTrySchedule(t *testing.T) {
  1842  	now := time.Now()
  1843  	tryEv := rTrySchedule{
  1844  		time: now.Add(time.Second + time.Millisecond),
  1845  	}
  1846  
  1847  	type args struct {
  1848  		event rTrySchedule
  1849  	}
  1850  	tests := []struct {
  1851  		name      string
  1852  		fields    scTestParams
  1853  		args      args
  1854  		wantEvent Event
  1855  		wantErr   bool
  1856  	}{
  1857  		{
  1858  			name:      "no peers",
  1859  			fields:    scTestParams{startTime: now, peers: map[string]*scPeer{}},
  1860  			args:      args{event: tryEv},
  1861  			wantEvent: noOpEvent{},
  1862  		},
  1863  		{
  1864  			name:      "only new peers",
  1865  			fields:    scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
  1866  			args:      args{event: tryEv},
  1867  			wantEvent: noOpEvent{},
  1868  		},
  1869  		{
  1870  			name:      "only Removed peers",
  1871  			fields:    scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
  1872  			args:      args{event: tryEv},
  1873  			wantEvent: noOpEvent{},
  1874  		},
  1875  		{
  1876  			name: "one Ready shorter peer",
  1877  			fields: scTestParams{
  1878  				startTime: now,
  1879  				height:    6,
  1880  				peers:     map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}},
  1881  			args:      args{event: tryEv},
  1882  			wantEvent: noOpEvent{},
  1883  		},
  1884  		{
  1885  			name: "one Ready equal peer",
  1886  			fields: scTestParams{
  1887  				startTime: now,
  1888  				peers:     map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
  1889  				allB:      []int64{1, 2, 3, 4}},
  1890  			args:      args{event: tryEv},
  1891  			wantEvent: scBlockRequest{peerID: "P1", height: 1},
  1892  		},
  1893  		{
  1894  			name: "many Ready higher peers with different number of pending requests",
  1895  			fields: scTestParams{
  1896  				startTime: now,
  1897  				peers: map[string]*scPeer{
  1898  					"P1": {height: 4, state: peerStateReady},
  1899  					"P2": {height: 5, state: peerStateReady}},
  1900  				allB: []int64{1, 2, 3, 4, 5},
  1901  				pending: map[int64]p2p.ID{
  1902  					1: "P1", 2: "P1",
  1903  					3: "P2",
  1904  				},
  1905  			},
  1906  			args:      args{event: tryEv},
  1907  			wantEvent: scBlockRequest{peerID: "P2", height: 4},
  1908  		},
  1909  
  1910  		{
  1911  			name: "many Ready higher peers with same number of pending requests",
  1912  			fields: scTestParams{
  1913  				startTime: now,
  1914  				peers: map[string]*scPeer{
  1915  					"P2": {height: 8, state: peerStateReady},
  1916  					"P1": {height: 8, state: peerStateReady},
  1917  					"P3": {height: 8, state: peerStateReady}},
  1918  				allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
  1919  				pending: map[int64]p2p.ID{
  1920  					1: "P1", 2: "P1",
  1921  					3: "P3", 4: "P3",
  1922  					5: "P2", 6: "P2",
  1923  				},
  1924  			},
  1925  			args:      args{event: tryEv},
  1926  			wantEvent: scBlockRequest{peerID: "P1", height: 7},
  1927  		},
  1928  	}
  1929  
  1930  	for _, tt := range tests {
  1931  		tt := tt
  1932  		t.Run(tt.name, func(t *testing.T) {
  1933  			sc := newTestScheduler(tt.fields)
  1934  			event, err := sc.handleTrySchedule(tt.args.event)
  1935  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  1936  		})
  1937  	}
  1938  }
  1939  
  1940  func TestScHandleStatusResponse(t *testing.T) {
  1941  	now := time.Now()
  1942  	statusRespP1Ev := bcStatusResponse{
  1943  		time:   now.Add(time.Second + time.Millisecond),
  1944  		peerID: "P1",
  1945  		height: 6,
  1946  	}
  1947  
  1948  	type args struct {
  1949  		event bcStatusResponse
  1950  	}
  1951  	tests := []struct {
  1952  		name      string
  1953  		fields    scTestParams
  1954  		args      args
  1955  		wantEvent Event
  1956  		wantErr   bool
  1957  	}{
  1958  		{
  1959  			name: "change height of non existing peer",
  1960  			fields: scTestParams{
  1961  				peers: map[string]*scPeer{"P2": {height: 2, state: peerStateReady}},
  1962  				allB:  []int64{1, 2},
  1963  			},
  1964  			args:      args{event: statusRespP1Ev},
  1965  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1966  		},
  1967  
  1968  		{
  1969  			name:      "increase height of removed peer",
  1970  			fields:    scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
  1971  			args:      args{event: statusRespP1Ev},
  1972  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1973  		},
  1974  
  1975  		{
  1976  			name: "decrease height of single peer",
  1977  			fields: scTestParams{
  1978  				height: 5,
  1979  				peers:  map[string]*scPeer{"P1": {height: 10, state: peerStateReady}},
  1980  				allB:   []int64{5, 6, 7, 8, 9, 10},
  1981  			},
  1982  			args:      args{event: statusRespP1Ev},
  1983  			wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
  1984  		},
  1985  
  1986  		{
  1987  			name: "increase height of single peer",
  1988  			fields: scTestParams{
  1989  				peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
  1990  				allB:  []int64{1, 2}},
  1991  			args:      args{event: statusRespP1Ev},
  1992  			wantEvent: noOpEvent{},
  1993  		},
  1994  		{
  1995  			name: "noop height change of single peer",
  1996  			fields: scTestParams{
  1997  				peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}},
  1998  				allB:  []int64{1, 2, 3, 4, 5, 6}},
  1999  			args:      args{event: statusRespP1Ev},
  2000  			wantEvent: noOpEvent{},
  2001  		},
  2002  	}
  2003  
  2004  	for _, tt := range tests {
  2005  		tt := tt
  2006  		t.Run(tt.name, func(t *testing.T) {
  2007  			sc := newTestScheduler(tt.fields)
  2008  			event, err := sc.handleStatusResponse(tt.args.event)
  2009  			checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
  2010  		})
  2011  	}
  2012  }
  2013  
  2014  func TestScHandle(t *testing.T) {
  2015  	now := time.Now()
  2016  
  2017  	type unknownEv struct {
  2018  		priorityNormal
  2019  	}
  2020  
  2021  	t0 := time.Now()
  2022  	tick := make([]time.Time, 100)
  2023  	for i := range tick {
  2024  		tick[i] = t0.Add(time.Duration(i) * time.Millisecond)
  2025  	}
  2026  
  2027  	type args struct {
  2028  		event Event
  2029  	}
  2030  	type scStep struct {
  2031  		currentSc *scTestParams
  2032  		args      args
  2033  		wantEvent Event
  2034  		wantErr   bool
  2035  		wantSc    *scTestParams
  2036  	}
  2037  	tests := []struct {
  2038  		name  string
  2039  		steps []scStep
  2040  	}{
  2041  		{
  2042  			name: "unknown event",
  2043  			steps: []scStep{
  2044  				{ // add P1
  2045  					currentSc: &scTestParams{},
  2046  					args:      args{event: unknownEv{}},
  2047  					wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")},
  2048  					wantSc:    &scTestParams{},
  2049  				},
  2050  			},
  2051  		},
  2052  		{
  2053  			name: "single peer, sync 3 blocks",
  2054  			steps: []scStep{
  2055  				{ // add P1
  2056  					currentSc: &scTestParams{startTime: now, peers: map[string]*scPeer{}, height: 1},
  2057  					args:      args{event: bcAddNewPeer{peerID: "P1"}},
  2058  					wantEvent: noOpEvent{},
  2059  					wantSc: &scTestParams{startTime: now, peers: map[string]*scPeer{
  2060  						"P1": {base: -1, height: -1, state: peerStateNew}}, height: 1},
  2061  				},
  2062  				{ // set height of P1
  2063  					args:      args{event: bcStatusResponse{peerID: "P1", time: tick[0], height: 3}},
  2064  					wantEvent: noOpEvent{},
  2065  					wantSc: &scTestParams{
  2066  						startTime: now,
  2067  						peers:     map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
  2068  						allB:      []int64{1, 2, 3},
  2069  						height:    1,
  2070  					},
  2071  				},
  2072  				{ // schedule block 1
  2073  					args:      args{event: rTrySchedule{time: tick[1]}},
  2074  					wantEvent: scBlockRequest{peerID: "P1", height: 1},
  2075  					wantSc: &scTestParams{
  2076  						startTime:   now,
  2077  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
  2078  						allB:        []int64{1, 2, 3},
  2079  						pending:     map[int64]p2p.ID{1: "P1"},
  2080  						pendingTime: map[int64]time.Time{1: tick[1]},
  2081  						height:      1,
  2082  					},
  2083  				},
  2084  				{ // schedule block 2
  2085  					args:      args{event: rTrySchedule{time: tick[2]}},
  2086  					wantEvent: scBlockRequest{peerID: "P1", height: 2},
  2087  					wantSc: &scTestParams{
  2088  						startTime:   now,
  2089  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
  2090  						allB:        []int64{1, 2, 3},
  2091  						pending:     map[int64]p2p.ID{1: "P1", 2: "P1"},
  2092  						pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2]},
  2093  						height:      1,
  2094  					},
  2095  				},
  2096  				{ // schedule block 3
  2097  					args:      args{event: rTrySchedule{time: tick[3]}},
  2098  					wantEvent: scBlockRequest{peerID: "P1", height: 3},
  2099  					wantSc: &scTestParams{
  2100  						startTime:   now,
  2101  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
  2102  						allB:        []int64{1, 2, 3},
  2103  						pending:     map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"},
  2104  						pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2], 3: tick[3]},
  2105  						height:      1,
  2106  					},
  2107  				},
  2108  				{ // block response 1
  2109  					args:      args{event: bcBlockResponse{peerID: "P1", time: tick[4], size: 100, block: makeScBlock(1)}},
  2110  					wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(1)},
  2111  					wantSc: &scTestParams{
  2112  						startTime:   now,
  2113  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[4]}},
  2114  						allB:        []int64{1, 2, 3},
  2115  						pending:     map[int64]p2p.ID{2: "P1", 3: "P1"},
  2116  						pendingTime: map[int64]time.Time{2: tick[2], 3: tick[3]},
  2117  						received:    map[int64]p2p.ID{1: "P1"},
  2118  						height:      1,
  2119  					},
  2120  				},
  2121  				{ // block response 2
  2122  					args:      args{event: bcBlockResponse{peerID: "P1", time: tick[5], size: 100, block: makeScBlock(2)}},
  2123  					wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(2)},
  2124  					wantSc: &scTestParams{
  2125  						startTime:   now,
  2126  						peers:       map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[5]}},
  2127  						allB:        []int64{1, 2, 3},
  2128  						pending:     map[int64]p2p.ID{3: "P1"},
  2129  						pendingTime: map[int64]time.Time{3: tick[3]},
  2130  						received:    map[int64]p2p.ID{1: "P1", 2: "P1"},
  2131  						height:      1,
  2132  					},
  2133  				},
  2134  				{ // block response 3
  2135  					args:      args{event: bcBlockResponse{peerID: "P1", time: tick[6], size: 100, block: makeScBlock(3)}},
  2136  					wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(3)},
  2137  					wantSc: &scTestParams{
  2138  						startTime: now,
  2139  						peers:     map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2140  						allB:      []int64{1, 2, 3},
  2141  						received:  map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"},
  2142  						height:    1,
  2143  					},
  2144  				},
  2145  				{ // processed block 1
  2146  					args:      args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 1}},
  2147  					wantEvent: noOpEvent{},
  2148  					wantSc: &scTestParams{
  2149  						startTime: now,
  2150  						peers:     map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2151  						allB:      []int64{2, 3},
  2152  						received:  map[int64]p2p.ID{2: "P1", 3: "P1"},
  2153  						height:    2,
  2154  					},
  2155  				},
  2156  				{ // processed block 2
  2157  					args:      args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 2}},
  2158  					wantEvent: scFinishedEv{},
  2159  					wantSc: &scTestParams{
  2160  						startTime: now,
  2161  						peers:     map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2162  						allB:      []int64{3},
  2163  						received:  map[int64]p2p.ID{3: "P1"},
  2164  						height:    3,
  2165  					},
  2166  				},
  2167  			},
  2168  		},
  2169  		{
  2170  			name: "block verification failure",
  2171  			steps: []scStep{
  2172  				{ // failure processing block 1
  2173  					currentSc: &scTestParams{
  2174  						startTime: now,
  2175  						peers: map[string]*scPeer{
  2176  							"P1": {height: 4, state: peerStateReady, lastTouched: tick[6]},
  2177  							"P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2178  						allB:     []int64{1, 2, 3, 4},
  2179  						received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"},
  2180  						height:   1,
  2181  					},
  2182  					args:      args{event: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P1"}},
  2183  					wantEvent: noOpEvent{},
  2184  					wantSc: &scTestParams{
  2185  						startTime: now,
  2186  						peers: map[string]*scPeer{
  2187  							"P1": {height: 4, state: peerStateRemoved, lastTouched: tick[6]},
  2188  							"P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
  2189  						allB:     []int64{1, 2, 3},
  2190  						received: map[int64]p2p.ID{},
  2191  						height:   1,
  2192  					},
  2193  				},
  2194  			},
  2195  		},
  2196  	}
  2197  
  2198  	for _, tt := range tests {
  2199  		tt := tt
  2200  		t.Run(tt.name, func(t *testing.T) {
  2201  			var sc *scheduler
  2202  			for i, step := range tt.steps {
  2203  				// First step must always initialise the currentState as state.
  2204  				if step.currentSc != nil {
  2205  					sc = newTestScheduler(*step.currentSc)
  2206  				}
  2207  				if sc == nil {
  2208  					panic("Bad (initial?) step")
  2209  				}
  2210  
  2211  				nextEvent, err := sc.handle(step.args.event)
  2212  				wantSc := newTestScheduler(*step.wantSc)
  2213  
  2214  				t.Logf("step %d(%v): %s", i, step.args.event, sc)
  2215  				checkSameScheduler(t, wantSc, sc)
  2216  
  2217  				checkScResults(t, step.wantErr, err, step.wantEvent, nextEvent)
  2218  
  2219  				// Next step may use the wantedState as their currentState.
  2220  				sc = newTestScheduler(*step.wantSc)
  2221  			}
  2222  		})
  2223  	}
  2224  }