github.com/vipernet-xyz/tm@v0.34.24/blockchain/v2/scheduler_test.go (about)

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