package v2
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/tendermint/tendermint/internal/p2p"
|
|
"github.com/tendermint/tendermint/state"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
type scTestParams struct {
|
|
peers map[string]*scPeer
|
|
initHeight int64
|
|
height int64
|
|
allB []int64
|
|
pending map[int64]p2p.NodeID
|
|
pendingTime map[int64]time.Time
|
|
received map[int64]p2p.NodeID
|
|
peerTimeout time.Duration
|
|
minRecvRate int64
|
|
targetPending int
|
|
startTime time.Time
|
|
syncTimeout time.Duration
|
|
}
|
|
|
|
func verifyScheduler(sc *scheduler) {
|
|
missing := 0
|
|
if sc.maxHeight() >= sc.height {
|
|
missing = int(math.Min(float64(sc.targetPending), float64(sc.maxHeight()-sc.height+1)))
|
|
}
|
|
if len(sc.blockStates) != missing {
|
|
panic(fmt.Sprintf("scheduler block length %d different than target %d", len(sc.blockStates), missing))
|
|
}
|
|
}
|
|
|
|
func newTestScheduler(params scTestParams) *scheduler {
|
|
peers := make(map[p2p.NodeID]*scPeer)
|
|
var maxHeight int64
|
|
|
|
initHeight := params.initHeight
|
|
if initHeight == 0 {
|
|
initHeight = 1
|
|
}
|
|
sc := newScheduler(initHeight, params.startTime)
|
|
if params.height != 0 {
|
|
sc.height = params.height
|
|
}
|
|
|
|
for id, peer := range params.peers {
|
|
peer.peerID = p2p.NodeID(id)
|
|
peers[p2p.NodeID(id)] = peer
|
|
if maxHeight < peer.height {
|
|
maxHeight = peer.height
|
|
}
|
|
}
|
|
for _, h := range params.allB {
|
|
sc.blockStates[h] = blockStateNew
|
|
}
|
|
for h, pid := range params.pending {
|
|
sc.blockStates[h] = blockStatePending
|
|
sc.pendingBlocks[h] = pid
|
|
}
|
|
for h, tm := range params.pendingTime {
|
|
sc.pendingTime[h] = tm
|
|
}
|
|
for h, pid := range params.received {
|
|
sc.blockStates[h] = blockStateReceived
|
|
sc.receivedBlocks[h] = pid
|
|
}
|
|
|
|
sc.peers = peers
|
|
sc.peerTimeout = params.peerTimeout
|
|
if params.syncTimeout == 0 {
|
|
sc.syncTimeout = 10 * time.Second
|
|
} else {
|
|
sc.syncTimeout = params.syncTimeout
|
|
}
|
|
|
|
if params.targetPending == 0 {
|
|
sc.targetPending = 10
|
|
} else {
|
|
sc.targetPending = params.targetPending
|
|
}
|
|
|
|
sc.minRecvRate = params.minRecvRate
|
|
|
|
verifyScheduler(sc)
|
|
|
|
return sc
|
|
}
|
|
|
|
func TestScInit(t *testing.T) {
|
|
var (
|
|
initHeight int64 = 5
|
|
sc = newScheduler(initHeight, time.Now())
|
|
)
|
|
assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(initHeight-1))
|
|
assert.Equal(t, blockStateUnknown, sc.getStateAtHeight(initHeight))
|
|
assert.Equal(t, blockStateUnknown, sc.getStateAtHeight(initHeight+1))
|
|
}
|
|
|
|
func TestScMaxHeights(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
sc scheduler
|
|
wantMax int64
|
|
}{
|
|
{
|
|
name: "no peers",
|
|
sc: scheduler{height: 11},
|
|
wantMax: 10,
|
|
},
|
|
{
|
|
name: "one ready peer",
|
|
sc: scheduler{
|
|
height: 3,
|
|
peers: map[p2p.NodeID]*scPeer{"P1": {height: 6, state: peerStateReady}},
|
|
},
|
|
wantMax: 6,
|
|
},
|
|
{
|
|
name: "ready and removed peers",
|
|
sc: scheduler{
|
|
height: 1,
|
|
peers: map[p2p.NodeID]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady},
|
|
"P2": {height: 10, state: peerStateRemoved}},
|
|
},
|
|
wantMax: 4,
|
|
},
|
|
{
|
|
name: "removed peers",
|
|
sc: scheduler{
|
|
height: 1,
|
|
peers: map[p2p.NodeID]*scPeer{
|
|
"P1": {height: 4, state: peerStateRemoved},
|
|
"P2": {height: 10, state: peerStateRemoved}},
|
|
},
|
|
wantMax: 0,
|
|
},
|
|
{
|
|
name: "new peers",
|
|
sc: scheduler{
|
|
height: 1,
|
|
peers: map[p2p.NodeID]*scPeer{
|
|
"P1": {base: -1, height: -1, state: peerStateNew},
|
|
"P2": {base: -1, height: -1, state: peerStateNew}},
|
|
},
|
|
wantMax: 0,
|
|
},
|
|
{
|
|
name: "mixed peers",
|
|
sc: scheduler{
|
|
height: 1,
|
|
peers: map[p2p.NodeID]*scPeer{
|
|
"P1": {height: -1, state: peerStateNew},
|
|
"P2": {height: 10, state: peerStateReady},
|
|
"P3": {height: 20, state: peerStateRemoved},
|
|
"P4": {height: 22, state: peerStateReady},
|
|
},
|
|
},
|
|
wantMax: 22,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// maxHeight() should not mutate the scheduler
|
|
wantSc := tt.sc
|
|
|
|
resMax := tt.sc.maxHeight()
|
|
assert.Equal(t, tt.wantMax, resMax)
|
|
assert.Equal(t, wantSc, tt.sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScEnsurePeer(t *testing.T) {
|
|
|
|
type args struct {
|
|
peerID p2p.NodeID
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantFields scTestParams
|
|
}{
|
|
{
|
|
name: "add first peer",
|
|
fields: scTestParams{},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {base: -1, height: -1, state: peerStateNew}}},
|
|
},
|
|
{
|
|
name: "add second peer",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {base: -1, height: -1, state: peerStateNew}}},
|
|
args: args{peerID: "P2"},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{
|
|
"P1": {base: -1, height: -1, state: peerStateNew},
|
|
"P2": {base: -1, height: -1, state: peerStateNew}}},
|
|
},
|
|
{
|
|
name: "add duplicate peer is fine",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
|
|
},
|
|
{
|
|
name: "add duplicate peer with existing peer in Ready state is noop",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 3}},
|
|
allB: []int64{1, 2, 3},
|
|
},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 3}},
|
|
allB: []int64{1, 2, 3},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
sc.ensurePeer(tt.args.peerID)
|
|
wantSc := newTestScheduler(tt.wantFields)
|
|
assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScTouchPeer(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
type args struct {
|
|
peerID p2p.NodeID
|
|
time time.Time
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantFields scTestParams
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "attempt to touch non existing peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 5}},
|
|
allB: []int64{1, 2, 3, 4, 5},
|
|
},
|
|
args: args{peerID: "P2", time: now},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 5}},
|
|
allB: []int64{1, 2, 3, 4, 5},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "attempt to touch peer in state New",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {}}},
|
|
args: args{peerID: "P1", time: now},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {}}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "attempt to touch peer in state Removed",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved}, "P2": {state: peerStateReady}}},
|
|
args: args{peerID: "P1", time: now},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved}, "P2": {state: peerStateReady}}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "touch peer in state Ready",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, lastTouched: now}}},
|
|
args: args{peerID: "P1", time: now.Add(3 * time.Second)},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{
|
|
"P1": {state: peerStateReady, lastTouched: now.Add(3 * time.Second)}}},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
if err := sc.touchPeer(tt.args.peerID, tt.args.time); (err != nil) != tt.wantErr {
|
|
t.Errorf("touchPeer() wantErr %v, error = %v", tt.wantErr, err)
|
|
}
|
|
wantSc := newTestScheduler(tt.wantFields)
|
|
assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScPrunablePeers(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
type args struct {
|
|
threshold time.Duration
|
|
time time.Time
|
|
minSpeed int64
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantResult []p2p.NodeID
|
|
}{
|
|
{
|
|
name: "no peers",
|
|
fields: scTestParams{peers: map[string]*scPeer{}},
|
|
args: args{threshold: time.Second, time: now.Add(time.Second + time.Millisecond), minSpeed: 100},
|
|
wantResult: []p2p.NodeID{},
|
|
},
|
|
{
|
|
name: "mixed peers",
|
|
fields: scTestParams{peers: map[string]*scPeer{
|
|
// X - removed, active, fast
|
|
"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101},
|
|
// X - ready, active, fast
|
|
"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101},
|
|
// X - removed, active, equal
|
|
"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100},
|
|
// V - ready, inactive, equal
|
|
"P4": {state: peerStateReady, lastTouched: now, lastRate: 100},
|
|
// V - ready, inactive, slow
|
|
"P5": {state: peerStateReady, lastTouched: now, lastRate: 99},
|
|
// V - ready, active, slow
|
|
"P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90},
|
|
}},
|
|
args: args{threshold: time.Second, time: now.Add(time.Second + time.Millisecond), minSpeed: 100},
|
|
wantResult: []p2p.NodeID{"P4", "P5", "P6"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
// peersSlowerThan should not mutate the scheduler
|
|
wantSc := sc
|
|
res := sc.prunablePeers(tt.args.threshold, tt.args.minSpeed, tt.args.time)
|
|
assert.Equal(t, tt.wantResult, res)
|
|
assert.Equal(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScRemovePeer(t *testing.T) {
|
|
|
|
type args struct {
|
|
peerID p2p.NodeID
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantFields scTestParams
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "remove non existing peer",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
|
|
args: args{peerID: "P2"},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
|
|
},
|
|
{
|
|
name: "remove single New peer",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateRemoved}}},
|
|
},
|
|
{
|
|
name: "remove one of two New peers",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}, "P2": {height: -1}}},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateRemoved}, "P2": {height: -1}}},
|
|
},
|
|
{
|
|
name: "remove one Ready peer, all peers removed",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 10, state: peerStateRemoved},
|
|
"P2": {height: 5, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5},
|
|
},
|
|
args: args{peerID: "P2"},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{
|
|
"P1": {height: 10, state: peerStateRemoved},
|
|
"P2": {height: 5, state: peerStateRemoved}},
|
|
},
|
|
},
|
|
{
|
|
name: "attempt to remove already removed peer",
|
|
fields: scTestParams{
|
|
height: 8,
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 10, state: peerStateRemoved},
|
|
"P2": {height: 11, state: peerStateReady}},
|
|
allB: []int64{8, 9, 10, 11},
|
|
},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{
|
|
height: 8,
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 10, state: peerStateRemoved},
|
|
"P2": {height: 11, state: peerStateReady}},
|
|
allB: []int64{8, 9, 10, 11}},
|
|
},
|
|
{
|
|
name: "remove Ready peer with blocks requested",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3},
|
|
pending: map[int64]p2p.NodeID{1: "P1"},
|
|
},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateRemoved}},
|
|
allB: []int64{},
|
|
pending: map[int64]p2p.NodeID{},
|
|
},
|
|
},
|
|
{
|
|
name: "remove Ready peer with blocks received",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3},
|
|
received: map[int64]p2p.NodeID{1: "P1"},
|
|
},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateRemoved}},
|
|
allB: []int64{},
|
|
received: map[int64]p2p.NodeID{},
|
|
},
|
|
},
|
|
{
|
|
name: "remove Ready peer with blocks received and requested (not yet received)",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 3: "P1"},
|
|
received: map[int64]p2p.NodeID{2: "P1", 4: "P1"},
|
|
},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}},
|
|
allB: []int64{},
|
|
pending: map[int64]p2p.NodeID{},
|
|
received: map[int64]p2p.NodeID{},
|
|
},
|
|
},
|
|
{
|
|
name: "remove Ready peer from multiple peers set, with blocks received and requested (not yet received)",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 6, state: peerStateReady},
|
|
"P2": {height: 6, state: peerStateReady},
|
|
},
|
|
allB: []int64{1, 2, 3, 4, 5, 6},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 3: "P2", 6: "P1"},
|
|
received: map[int64]p2p.NodeID{2: "P1", 4: "P2", 5: "P2"},
|
|
},
|
|
args: args{peerID: "P1"},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 6, state: peerStateRemoved},
|
|
"P2": {height: 6, state: peerStateReady},
|
|
},
|
|
allB: []int64{1, 2, 3, 4, 5, 6},
|
|
pending: map[int64]p2p.NodeID{3: "P2"},
|
|
received: map[int64]p2p.NodeID{4: "P2", 5: "P2"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
sc.removePeer(tt.args.peerID)
|
|
wantSc := newTestScheduler(tt.wantFields)
|
|
assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScSetPeerRange(t *testing.T) {
|
|
|
|
type args struct {
|
|
peerID p2p.NodeID
|
|
base int64
|
|
height int64
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantFields scTestParams
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "change height of non existing peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2}},
|
|
args: args{peerID: "P2", height: 4},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 2, state: peerStateReady},
|
|
"P2": {height: 4, state: peerStateReady},
|
|
},
|
|
allB: []int64{1, 2, 3, 4}},
|
|
},
|
|
{
|
|
name: "increase height of removed peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
|
|
args: args{peerID: "P1", height: 4},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
|
|
},
|
|
{
|
|
name: "decrease height of single peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4}},
|
|
args: args{peerID: "P1", height: 2},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}},
|
|
allB: []int64{}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "increase height of single peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2}},
|
|
args: args{peerID: "P1", height: 4},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4}},
|
|
},
|
|
{
|
|
name: "noop height change of single peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4}},
|
|
args: args{peerID: "P1", height: 4},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4}},
|
|
},
|
|
{
|
|
name: "add peer with huge height 10**10 ",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P2": {height: -1, state: peerStateNew}},
|
|
targetPending: 4,
|
|
},
|
|
args: args{peerID: "P2", height: 10000000000},
|
|
wantFields: scTestParams{
|
|
targetPending: 4,
|
|
peers: map[string]*scPeer{"P2": {height: 10000000000, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4}},
|
|
},
|
|
{
|
|
name: "add peer with base > height should error",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4}},
|
|
args: args{peerID: "P1", base: 6, height: 5},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "add peer with base == height is fine",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateNew}},
|
|
targetPending: 4,
|
|
},
|
|
args: args{peerID: "P1", base: 6, height: 6},
|
|
wantFields: scTestParams{
|
|
targetPending: 4,
|
|
peers: map[string]*scPeer{"P1": {base: 6, height: 6, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4}},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
err := sc.setPeerRange(tt.args.peerID, tt.args.base, tt.args.height)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("setPeerHeight() wantErr %v, error = %v", tt.wantErr, err)
|
|
}
|
|
wantSc := newTestScheduler(tt.wantFields)
|
|
assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScGetPeersWithHeight(t *testing.T) {
|
|
|
|
type args struct {
|
|
height int64
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantResult []p2p.NodeID
|
|
}{
|
|
{
|
|
name: "no peers",
|
|
fields: scTestParams{peers: map[string]*scPeer{}},
|
|
args: args{height: 10},
|
|
wantResult: []p2p.NodeID{},
|
|
},
|
|
{
|
|
name: "only new peers",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
|
|
args: args{height: 10},
|
|
wantResult: []p2p.NodeID{},
|
|
},
|
|
{
|
|
name: "only Removed peers",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
|
|
args: args{height: 2},
|
|
wantResult: []p2p.NodeID{},
|
|
},
|
|
{
|
|
name: "one Ready shorter peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
args: args{height: 5},
|
|
wantResult: []p2p.NodeID{},
|
|
},
|
|
{
|
|
name: "one Ready equal peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
args: args{height: 4},
|
|
wantResult: []p2p.NodeID{"P1"},
|
|
},
|
|
{
|
|
name: "one Ready higher peer",
|
|
fields: scTestParams{
|
|
targetPending: 4,
|
|
peers: map[string]*scPeer{"P1": {height: 20, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
args: args{height: 4},
|
|
wantResult: []p2p.NodeID{"P1"},
|
|
},
|
|
{
|
|
name: "one Ready higher peer at base",
|
|
fields: scTestParams{
|
|
targetPending: 4,
|
|
peers: map[string]*scPeer{"P1": {base: 4, height: 20, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
args: args{height: 4},
|
|
wantResult: []p2p.NodeID{"P1"},
|
|
},
|
|
{
|
|
name: "one Ready higher peer with higher base",
|
|
fields: scTestParams{
|
|
targetPending: 4,
|
|
peers: map[string]*scPeer{"P1": {base: 10, height: 20, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
args: args{height: 4},
|
|
wantResult: []p2p.NodeID{},
|
|
},
|
|
{
|
|
name: "multiple mixed peers",
|
|
fields: scTestParams{
|
|
height: 8,
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: -1, state: peerStateNew},
|
|
"P2": {height: 10, state: peerStateReady},
|
|
"P3": {height: 5, state: peerStateReady},
|
|
"P4": {height: 20, state: peerStateRemoved},
|
|
"P5": {height: 11, state: peerStateReady}},
|
|
allB: []int64{8, 9, 10, 11},
|
|
},
|
|
args: args{height: 8},
|
|
wantResult: []p2p.NodeID{"P2", "P5"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
// getPeersWithHeight should not mutate the scheduler
|
|
wantSc := sc
|
|
res := sc.getPeersWithHeight(tt.args.height)
|
|
sort.Sort(PeerByID(res))
|
|
assert.Equal(t, tt.wantResult, res)
|
|
assert.Equal(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScMarkPending(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
type args struct {
|
|
peerID p2p.NodeID
|
|
height int64
|
|
tm time.Time
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantFields scTestParams
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "attempt mark pending an unknown block above height",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2}},
|
|
args: args{peerID: "P1", height: 3, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "attempt mark pending an unknown block below base",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6}},
|
|
args: args{peerID: "P1", height: 3, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "attempt mark pending from non existing peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2}},
|
|
args: args{peerID: "P2", height: 1, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "mark pending from Removed peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
|
|
args: args{peerID: "P1", height: 1, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "mark pending from New peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady},
|
|
"P2": {height: 4, state: peerStateNew},
|
|
},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
args: args{peerID: "P2", height: 2, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady},
|
|
"P2": {height: 4, state: peerStateNew},
|
|
},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "mark pending from short peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady},
|
|
"P2": {height: 2, state: peerStateReady},
|
|
},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
args: args{peerID: "P2", height: 3, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady},
|
|
"P2": {height: 2, state: peerStateReady},
|
|
},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "mark pending all good",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2},
|
|
pending: map[int64]p2p.NodeID{1: "P1"},
|
|
pendingTime: map[int64]time.Time{1: now},
|
|
},
|
|
args: args{peerID: "P1", height: 2, tm: now.Add(time.Millisecond)},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P1"},
|
|
pendingTime: map[int64]time.Time{1: now, 2: now.Add(time.Millisecond)},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
if err := sc.markPending(tt.args.peerID, tt.args.height, tt.args.tm); (err != nil) != tt.wantErr {
|
|
t.Errorf("markPending() wantErr %v, error = %v", tt.wantErr, err)
|
|
}
|
|
wantSc := newTestScheduler(tt.wantFields)
|
|
assert.Equal(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScMarkReceived(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
type args struct {
|
|
peerID p2p.NodeID
|
|
height int64
|
|
size int64
|
|
tm time.Time
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantFields scTestParams
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "received from non existing peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2}},
|
|
args: args{peerID: "P2", height: 1, size: 1000, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "received from removed peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
|
|
args: args{peerID: "P1", height: 1, size: 1000, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "received from unsolicited peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady},
|
|
"P2": {height: 4, state: peerStateReady},
|
|
},
|
|
allB: []int64{1, 2, 3, 4},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P2", 3: "P2", 4: "P1"},
|
|
},
|
|
args: args{peerID: "P1", height: 2, size: 1000, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady},
|
|
"P2": {height: 4, state: peerStateReady},
|
|
},
|
|
allB: []int64{1, 2, 3, 4},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P2", 3: "P2", 4: "P1"},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "received but blockRequest not sent",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
pending: map[int64]p2p.NodeID{},
|
|
},
|
|
args: args{peerID: "P1", height: 2, size: 1000, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
pending: map[int64]p2p.NodeID{},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "received with bad timestamp",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P1"},
|
|
pendingTime: map[int64]time.Time{1: now, 2: now.Add(time.Second)},
|
|
},
|
|
args: args{peerID: "P1", height: 2, size: 1000, tm: now},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P1"},
|
|
pendingTime: map[int64]time.Time{1: now, 2: now.Add(time.Second)},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "received all good",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P1"},
|
|
pendingTime: map[int64]time.Time{1: now, 2: now},
|
|
},
|
|
args: args{peerID: "P1", height: 2, size: 1000, tm: now.Add(time.Millisecond)},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2},
|
|
pending: map[int64]p2p.NodeID{1: "P1"},
|
|
pendingTime: map[int64]time.Time{1: now},
|
|
received: map[int64]p2p.NodeID{2: "P1"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
if err := sc.markReceived(
|
|
tt.args.peerID,
|
|
tt.args.height,
|
|
tt.args.size,
|
|
now.Add(time.Second)); (err != nil) != tt.wantErr {
|
|
t.Errorf("markReceived() wantErr %v, error = %v", tt.wantErr, err)
|
|
}
|
|
wantSc := newTestScheduler(tt.wantFields)
|
|
assert.Equal(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScMarkProcessed(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
type args struct {
|
|
height int64
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantFields scTestParams
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "processed an unreceived block",
|
|
fields: scTestParams{
|
|
height: 2,
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{2},
|
|
pending: map[int64]p2p.NodeID{2: "P1"},
|
|
pendingTime: map[int64]time.Time{2: now},
|
|
targetPending: 1,
|
|
},
|
|
args: args{height: 2},
|
|
wantFields: scTestParams{
|
|
height: 3,
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{3},
|
|
targetPending: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "mark processed success",
|
|
fields: scTestParams{
|
|
height: 1,
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2},
|
|
pending: map[int64]p2p.NodeID{2: "P1"},
|
|
pendingTime: map[int64]time.Time{2: now},
|
|
received: map[int64]p2p.NodeID{1: "P1"}},
|
|
args: args{height: 1},
|
|
wantFields: scTestParams{
|
|
height: 2,
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{2},
|
|
pending: map[int64]p2p.NodeID{2: "P1"},
|
|
pendingTime: map[int64]time.Time{2: now}},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
oldBlockState := sc.getStateAtHeight(tt.args.height)
|
|
if err := sc.markProcessed(tt.args.height); (err != nil) != tt.wantErr {
|
|
t.Errorf("markProcessed() wantErr %v, error = %v", tt.wantErr, err)
|
|
}
|
|
if tt.wantErr {
|
|
assert.Equal(t, oldBlockState, sc.getStateAtHeight(tt.args.height))
|
|
} else {
|
|
assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(tt.args.height))
|
|
}
|
|
wantSc := newTestScheduler(tt.wantFields)
|
|
checkSameScheduler(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScResetState(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
state state.State
|
|
wantFields scTestParams
|
|
}{
|
|
{
|
|
name: "updates height and initHeight",
|
|
fields: scTestParams{
|
|
height: 0,
|
|
initHeight: 0,
|
|
},
|
|
state: state.State{LastBlockHeight: 7},
|
|
wantFields: scTestParams{
|
|
height: 8,
|
|
initHeight: 8,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
e, err := sc.handleResetState(bcResetState{state: tt.state})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, e, noOp)
|
|
wantSc := newTestScheduler(tt.wantFields)
|
|
checkSameScheduler(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScAllBlocksProcessed(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
wantResult bool
|
|
}{
|
|
{
|
|
name: "no blocks, no peers",
|
|
fields: scTestParams{},
|
|
wantResult: false,
|
|
},
|
|
{
|
|
name: "only New blocks",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
wantResult: false,
|
|
},
|
|
{
|
|
name: "only Pending blocks",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
|
|
pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now},
|
|
},
|
|
wantResult: false,
|
|
},
|
|
{
|
|
name: "only Received blocks",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
received: map[int64]p2p.NodeID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
|
|
},
|
|
wantResult: false,
|
|
},
|
|
{
|
|
name: "only Processed blocks plus highest is received",
|
|
fields: scTestParams{
|
|
height: 4,
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{4},
|
|
received: map[int64]p2p.NodeID{4: "P1"},
|
|
},
|
|
wantResult: true,
|
|
},
|
|
{
|
|
name: "mixed block states",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
pending: map[int64]p2p.NodeID{2: "P1", 4: "P1"},
|
|
pendingTime: map[int64]time.Time{2: now, 4: now},
|
|
},
|
|
wantResult: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
// allBlocksProcessed() should not mutate the scheduler
|
|
wantSc := sc
|
|
res := sc.allBlocksProcessed()
|
|
assert.Equal(t, tt.wantResult, res)
|
|
checkSameScheduler(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScNextHeightToSchedule(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
wantHeight int64
|
|
}{
|
|
{
|
|
name: "no blocks",
|
|
fields: scTestParams{initHeight: 11, height: 11},
|
|
wantHeight: -1,
|
|
},
|
|
{
|
|
name: "only New blocks",
|
|
fields: scTestParams{
|
|
initHeight: 3,
|
|
peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}},
|
|
allB: []int64{3, 4, 5, 6},
|
|
},
|
|
wantHeight: 3,
|
|
},
|
|
{
|
|
name: "only Pending blocks",
|
|
fields: scTestParams{
|
|
initHeight: 1,
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
|
|
pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now},
|
|
},
|
|
wantHeight: -1,
|
|
},
|
|
{
|
|
name: "only Received blocks",
|
|
fields: scTestParams{
|
|
initHeight: 1,
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
received: map[int64]p2p.NodeID{1: "P1", 2: "P1", 3: "P1", 4: "P1"},
|
|
},
|
|
wantHeight: -1,
|
|
},
|
|
{
|
|
name: "only Processed blocks",
|
|
fields: scTestParams{
|
|
initHeight: 1,
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
wantHeight: 1,
|
|
},
|
|
{
|
|
name: "mixed block states",
|
|
fields: scTestParams{
|
|
initHeight: 1,
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
pending: map[int64]p2p.NodeID{2: "P1"},
|
|
pendingTime: map[int64]time.Time{2: now},
|
|
},
|
|
wantHeight: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
// nextHeightToSchedule() should not mutate the scheduler
|
|
wantSc := sc
|
|
|
|
resMin := sc.nextHeightToSchedule()
|
|
assert.Equal(t, tt.wantHeight, resMin)
|
|
checkSameScheduler(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScSelectPeer(t *testing.T) {
|
|
|
|
type args struct {
|
|
height int64
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantResult p2p.NodeID
|
|
wantError bool
|
|
}{
|
|
{
|
|
name: "no peers",
|
|
fields: scTestParams{peers: map[string]*scPeer{}},
|
|
args: args{height: 10},
|
|
wantResult: "",
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "only new peers",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
|
|
args: args{height: 10},
|
|
wantResult: "",
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "only Removed peers",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
|
|
args: args{height: 2},
|
|
wantResult: "",
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "one Ready shorter peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
args: args{height: 5},
|
|
wantResult: "",
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "one Ready equal peer",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
},
|
|
args: args{height: 4},
|
|
wantResult: "P1",
|
|
},
|
|
{
|
|
name: "one Ready higher peer",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6},
|
|
},
|
|
args: args{height: 4},
|
|
wantResult: "P1",
|
|
},
|
|
{
|
|
name: "one Ready higher peer with higher base",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6},
|
|
},
|
|
args: args{height: 3},
|
|
wantResult: "",
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "many Ready higher peers with different number of pending requests",
|
|
fields: scTestParams{
|
|
height: 4,
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 8, state: peerStateReady},
|
|
"P2": {height: 9, state: peerStateReady}},
|
|
allB: []int64{4, 5, 6, 7, 8, 9},
|
|
pending: map[int64]p2p.NodeID{
|
|
4: "P1", 6: "P1",
|
|
5: "P2",
|
|
},
|
|
},
|
|
args: args{height: 4},
|
|
wantResult: "P2",
|
|
},
|
|
{
|
|
name: "many Ready higher peers with same number of pending requests",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{
|
|
"P2": {height: 20, state: peerStateReady},
|
|
"P1": {height: 15, state: peerStateReady},
|
|
"P3": {height: 15, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
|
pending: map[int64]p2p.NodeID{
|
|
1: "P1", 2: "P1",
|
|
3: "P3", 4: "P3",
|
|
5: "P2", 6: "P2",
|
|
},
|
|
},
|
|
args: args{height: 7},
|
|
wantResult: "P1",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
// selectPeer should not mutate the scheduler
|
|
wantSc := sc
|
|
res, err := sc.selectPeer(tt.args.height)
|
|
assert.Equal(t, tt.wantResult, res)
|
|
assert.Equal(t, tt.wantError, err != nil)
|
|
checkSameScheduler(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
// makeScBlock makes an empty block.
|
|
func makeScBlock(height int64) *types.Block {
|
|
return &types.Block{Header: types.Header{Height: height}}
|
|
}
|
|
|
|
// used in place of assert.Equal(t, want, actual) to avoid failures due to
|
|
// scheduler.lastAdvanced timestamp inequalities.
|
|
func checkSameScheduler(t *testing.T, want *scheduler, actual *scheduler) {
|
|
assert.Equal(t, want.initHeight, actual.initHeight)
|
|
assert.Equal(t, want.height, actual.height)
|
|
assert.Equal(t, want.peers, actual.peers)
|
|
assert.Equal(t, want.blockStates, actual.blockStates)
|
|
assert.Equal(t, want.pendingBlocks, actual.pendingBlocks)
|
|
assert.Equal(t, want.pendingTime, actual.pendingTime)
|
|
assert.Equal(t, want.blockStates, actual.blockStates)
|
|
assert.Equal(t, want.receivedBlocks, actual.receivedBlocks)
|
|
assert.Equal(t, want.blockStates, actual.blockStates)
|
|
}
|
|
|
|
// checkScResults checks scheduler handler test results
|
|
func checkScResults(t *testing.T, wantErr bool, err error, wantEvent Event, event Event) {
|
|
if (err != nil) != wantErr {
|
|
t.Errorf("error = %v, wantErr %v", err, wantErr)
|
|
return
|
|
}
|
|
if !assert.IsType(t, wantEvent, event) {
|
|
t.Log(fmt.Sprintf("Wrong type received, got: %v", event))
|
|
}
|
|
switch wantEvent := wantEvent.(type) {
|
|
case scPeerError:
|
|
assert.Equal(t, wantEvent.peerID, event.(scPeerError).peerID)
|
|
assert.Equal(t, wantEvent.reason != nil, event.(scPeerError).reason != nil)
|
|
case scBlockReceived:
|
|
assert.Equal(t, wantEvent.peerID, event.(scBlockReceived).peerID)
|
|
assert.Equal(t, wantEvent.block, event.(scBlockReceived).block)
|
|
case scSchedulerFail:
|
|
assert.Equal(t, wantEvent.reason != nil, event.(scSchedulerFail).reason != nil)
|
|
}
|
|
}
|
|
|
|
func TestScHandleBlockResponse(t *testing.T) {
|
|
now := time.Now()
|
|
block6FromP1 := bcBlockResponse{
|
|
time: now.Add(time.Millisecond),
|
|
peerID: p2p.NodeID("P1"),
|
|
size: 100,
|
|
block: makeScBlock(6),
|
|
}
|
|
|
|
type args struct {
|
|
event bcBlockResponse
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantEvent Event
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "empty scheduler",
|
|
fields: scTestParams{},
|
|
args: args{event: block6FromP1},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "block from removed peer",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
|
|
args: args{event: block6FromP1},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "block we haven't asked for",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}},
|
|
args: args{event: block6FromP1},
|
|
wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
|
|
},
|
|
{
|
|
name: "block from wrong peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{6: "P2"},
|
|
pendingTime: map[int64]time.Time{6: now},
|
|
},
|
|
args: args{event: block6FromP1},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "block with bad timestamp",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{6: "P1"},
|
|
pendingTime: map[int64]time.Time{6: now.Add(time.Second)},
|
|
},
|
|
args: args{event: block6FromP1},
|
|
wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
|
|
},
|
|
{
|
|
name: "good block, accept",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{6: "P1"},
|
|
pendingTime: map[int64]time.Time{6: now},
|
|
},
|
|
args: args{event: block6FromP1},
|
|
wantEvent: scBlockReceived{peerID: "P1", block: block6FromP1.block},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
event, err := sc.handleBlockResponse(tt.args.event)
|
|
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScHandleNoBlockResponse(t *testing.T) {
|
|
now := time.Now()
|
|
noBlock6FromP1 := bcNoBlockResponse{
|
|
time: now.Add(time.Millisecond),
|
|
peerID: p2p.NodeID("P1"),
|
|
height: 6,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
wantEvent Event
|
|
wantFields scTestParams
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "empty scheduler",
|
|
fields: scTestParams{},
|
|
wantEvent: noOpEvent{},
|
|
wantFields: scTestParams{},
|
|
},
|
|
{
|
|
name: "noBlock from removed peer",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
|
|
wantEvent: noOpEvent{},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
|
|
},
|
|
{
|
|
name: "for block we haven't asked for",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}},
|
|
wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
|
|
},
|
|
{
|
|
name: "noBlock from peer we don't have",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{6: "P2"},
|
|
pendingTime: map[int64]time.Time{6: now},
|
|
},
|
|
wantEvent: noOpEvent{},
|
|
wantFields: scTestParams{
|
|
peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{6: "P2"},
|
|
pendingTime: map[int64]time.Time{6: now},
|
|
},
|
|
},
|
|
{
|
|
name: "noBlock from existing peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{6: "P1"},
|
|
pendingTime: map[int64]time.Time{6: now},
|
|
},
|
|
wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
|
|
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
event, err := sc.handleNoBlockResponse(noBlock6FromP1)
|
|
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
|
|
wantSc := newTestScheduler(tt.wantFields)
|
|
assert.Equal(t, wantSc, sc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScHandleBlockProcessed(t *testing.T) {
|
|
now := time.Now()
|
|
processed6FromP1 := pcBlockProcessed{
|
|
peerID: p2p.NodeID("P1"),
|
|
height: 6,
|
|
}
|
|
|
|
type args struct {
|
|
event pcBlockProcessed
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantEvent Event
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "empty scheduler",
|
|
fields: scTestParams{height: 6},
|
|
args: args{event: processed6FromP1},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "processed block we don't have",
|
|
fields: scTestParams{
|
|
initHeight: 6,
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
|
|
allB: []int64{6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{6: "P1"},
|
|
pendingTime: map[int64]time.Time{6: now},
|
|
},
|
|
args: args{event: processed6FromP1},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "processed block ok, we processed all blocks",
|
|
fields: scTestParams{
|
|
initHeight: 6,
|
|
peers: map[string]*scPeer{"P1": {height: 7, state: peerStateReady}},
|
|
allB: []int64{6, 7},
|
|
received: map[int64]p2p.NodeID{6: "P1", 7: "P1"},
|
|
},
|
|
args: args{event: processed6FromP1},
|
|
wantEvent: scFinishedEv{},
|
|
},
|
|
{
|
|
name: "processed block ok, we still have blocks to process",
|
|
fields: scTestParams{
|
|
initHeight: 6,
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
|
|
allB: []int64{6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{7: "P1", 8: "P1"},
|
|
received: map[int64]p2p.NodeID{6: "P1"},
|
|
},
|
|
args: args{event: processed6FromP1},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
event, err := sc.handleBlockProcessed(tt.args.event)
|
|
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScHandleBlockVerificationFailure(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
type args struct {
|
|
event pcBlockVerificationFailure
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantEvent Event
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "empty scheduler",
|
|
fields: scTestParams{},
|
|
args: args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "failed block we don't have, single peer is still removed",
|
|
fields: scTestParams{
|
|
initHeight: 6,
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
|
|
allB: []int64{6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{6: "P1"},
|
|
pendingTime: map[int64]time.Time{6: now},
|
|
},
|
|
args: args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}},
|
|
wantEvent: scFinishedEv{},
|
|
},
|
|
{
|
|
name: "failed block we don't have, one of two peers are removed",
|
|
fields: scTestParams{
|
|
initHeight: 6,
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}},
|
|
allB: []int64{6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{6: "P1"},
|
|
pendingTime: map[int64]time.Time{6: now},
|
|
},
|
|
args: args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "failed block, all blocks are processed after removal",
|
|
fields: scTestParams{
|
|
initHeight: 6,
|
|
peers: map[string]*scPeer{"P1": {height: 7, state: peerStateReady}},
|
|
allB: []int64{6, 7},
|
|
received: map[int64]p2p.NodeID{6: "P1", 7: "P1"},
|
|
},
|
|
args: args{event: pcBlockVerificationFailure{height: 7, firstPeerID: "P1", secondPeerID: "P1"}},
|
|
wantEvent: scFinishedEv{},
|
|
},
|
|
{
|
|
name: "failed block, we still have blocks to process",
|
|
fields: scTestParams{
|
|
initHeight: 5,
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}},
|
|
allB: []int64{5, 6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{7: "P1", 8: "P1"},
|
|
received: map[int64]p2p.NodeID{5: "P1", 6: "P1"},
|
|
},
|
|
args: args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P1"}},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "failed block, H+1 and H+2 delivered by different peers, we still have blocks to process",
|
|
fields: scTestParams{
|
|
initHeight: 5,
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 8, state: peerStateReady},
|
|
"P2": {height: 8, state: peerStateReady},
|
|
"P3": {height: 8, state: peerStateReady},
|
|
},
|
|
allB: []int64{5, 6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{7: "P1", 8: "P1"},
|
|
received: map[int64]p2p.NodeID{5: "P1", 6: "P1"},
|
|
},
|
|
args: args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P2"}},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
event, err := sc.handleBlockProcessError(tt.args.event)
|
|
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScHandleAddNewPeer(t *testing.T) {
|
|
addP1 := bcAddNewPeer{
|
|
peerID: p2p.NodeID("P1"),
|
|
}
|
|
type args struct {
|
|
event bcAddNewPeer
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantEvent Event
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "add P1 to empty scheduler",
|
|
fields: scTestParams{},
|
|
args: args{event: addP1},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "add duplicate peer",
|
|
fields: scTestParams{
|
|
initHeight: 6,
|
|
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
|
|
allB: []int64{6, 7, 8},
|
|
},
|
|
args: args{event: addP1},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "add P1 to non empty scheduler",
|
|
fields: scTestParams{
|
|
initHeight: 6,
|
|
peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
|
|
allB: []int64{6, 7, 8},
|
|
},
|
|
args: args{event: addP1},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
event, err := sc.handleAddNewPeer(tt.args.event)
|
|
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScHandleTryPrunePeer(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
pruneEv := rTryPrunePeer{
|
|
time: now.Add(time.Second + time.Millisecond),
|
|
}
|
|
type args struct {
|
|
event rTryPrunePeer
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantEvent Event
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "no peers",
|
|
fields: scTestParams{},
|
|
args: args{event: pruneEv},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "no prunable peers",
|
|
fields: scTestParams{
|
|
minRecvRate: 100,
|
|
peers: map[string]*scPeer{
|
|
// X - removed, active, fast
|
|
"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101},
|
|
// X - ready, active, fast
|
|
"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101},
|
|
// X - removed, active, equal
|
|
"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100}},
|
|
peerTimeout: time.Second,
|
|
},
|
|
args: args{event: pruneEv},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "mixed peers",
|
|
fields: scTestParams{
|
|
minRecvRate: 100,
|
|
peers: map[string]*scPeer{
|
|
// X - removed, active, fast
|
|
"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
|
|
// X - ready, active, fast
|
|
"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
|
|
// X - removed, active, equal
|
|
"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5},
|
|
// V - ready, inactive, equal
|
|
"P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7},
|
|
// V - ready, inactive, slow
|
|
"P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7},
|
|
// V - ready, active, slow
|
|
"P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7},
|
|
},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7},
|
|
peerTimeout: time.Second},
|
|
args: args{event: pruneEv},
|
|
wantEvent: scPeersPruned{peers: []p2p.NodeID{"P4", "P5", "P6"}},
|
|
},
|
|
{
|
|
name: "mixed peers, finish after pruning",
|
|
fields: scTestParams{
|
|
minRecvRate: 100,
|
|
height: 6,
|
|
peers: map[string]*scPeer{
|
|
// X - removed, active, fast
|
|
"P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
|
|
// X - ready, active, fast
|
|
"P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5},
|
|
// X - removed, active, equal
|
|
"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5},
|
|
// V - ready, inactive, equal
|
|
"P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7},
|
|
// V - ready, inactive, slow
|
|
"P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7},
|
|
// V - ready, active, slow
|
|
"P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7},
|
|
},
|
|
allB: []int64{6, 7},
|
|
peerTimeout: time.Second},
|
|
args: args{event: pruneEv},
|
|
wantEvent: scFinishedEv{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
event, err := sc.handleTryPrunePeer(tt.args.event)
|
|
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScHandleTrySchedule(t *testing.T) {
|
|
now := time.Now()
|
|
tryEv := rTrySchedule{
|
|
time: now.Add(time.Second + time.Millisecond),
|
|
}
|
|
|
|
type args struct {
|
|
event rTrySchedule
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantEvent Event
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "no peers",
|
|
fields: scTestParams{startTime: now, peers: map[string]*scPeer{}},
|
|
args: args{event: tryEv},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "only new peers",
|
|
fields: scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
|
|
args: args{event: tryEv},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "only Removed peers",
|
|
fields: scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
|
|
args: args{event: tryEv},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "one Ready shorter peer",
|
|
fields: scTestParams{
|
|
startTime: now,
|
|
height: 6,
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}},
|
|
args: args{event: tryEv},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "one Ready equal peer",
|
|
fields: scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4}},
|
|
args: args{event: tryEv},
|
|
wantEvent: scBlockRequest{peerID: "P1", height: 1},
|
|
},
|
|
{
|
|
name: "many Ready higher peers with different number of pending requests",
|
|
fields: scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady},
|
|
"P2": {height: 5, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5},
|
|
pending: map[int64]p2p.NodeID{
|
|
1: "P1", 2: "P1",
|
|
3: "P2",
|
|
},
|
|
},
|
|
args: args{event: tryEv},
|
|
wantEvent: scBlockRequest{peerID: "P2", height: 4},
|
|
},
|
|
|
|
{
|
|
name: "many Ready higher peers with same number of pending requests",
|
|
fields: scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{
|
|
"P2": {height: 8, state: peerStateReady},
|
|
"P1": {height: 8, state: peerStateReady},
|
|
"P3": {height: 8, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
|
|
pending: map[int64]p2p.NodeID{
|
|
1: "P1", 2: "P1",
|
|
3: "P3", 4: "P3",
|
|
5: "P2", 6: "P2",
|
|
},
|
|
},
|
|
args: args{event: tryEv},
|
|
wantEvent: scBlockRequest{peerID: "P1", height: 7},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
event, err := sc.handleTrySchedule(tt.args.event)
|
|
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScHandleStatusResponse(t *testing.T) {
|
|
now := time.Now()
|
|
statusRespP1Ev := bcStatusResponse{
|
|
time: now.Add(time.Second + time.Millisecond),
|
|
peerID: "P1",
|
|
height: 6,
|
|
}
|
|
|
|
type args struct {
|
|
event bcStatusResponse
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields scTestParams
|
|
args args
|
|
wantEvent Event
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "change height of non existing peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P2": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2},
|
|
},
|
|
args: args{event: statusRespP1Ev},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
|
|
{
|
|
name: "increase height of removed peer",
|
|
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}},
|
|
args: args{event: statusRespP1Ev},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
|
|
{
|
|
name: "decrease height of single peer",
|
|
fields: scTestParams{
|
|
height: 5,
|
|
peers: map[string]*scPeer{"P1": {height: 10, state: peerStateReady}},
|
|
allB: []int64{5, 6, 7, 8, 9, 10},
|
|
},
|
|
args: args{event: statusRespP1Ev},
|
|
wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
|
|
},
|
|
|
|
{
|
|
name: "increase height of single peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
|
allB: []int64{1, 2}},
|
|
args: args{event: statusRespP1Ev},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
{
|
|
name: "noop height change of single peer",
|
|
fields: scTestParams{
|
|
peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3, 4, 5, 6}},
|
|
args: args{event: statusRespP1Ev},
|
|
wantEvent: noOpEvent{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sc := newTestScheduler(tt.fields)
|
|
event, err := sc.handleStatusResponse(tt.args.event)
|
|
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScHandle(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
type unknownEv struct {
|
|
priorityNormal
|
|
}
|
|
|
|
block1, block2, block3 := makeScBlock(1), makeScBlock(2), makeScBlock(3)
|
|
|
|
t0 := time.Now()
|
|
tick := make([]time.Time, 100)
|
|
for i := range tick {
|
|
tick[i] = t0.Add(time.Duration(i) * time.Millisecond)
|
|
}
|
|
|
|
type args struct {
|
|
event Event
|
|
}
|
|
type scStep struct {
|
|
currentSc *scTestParams
|
|
args args
|
|
wantEvent Event
|
|
wantErr bool
|
|
wantSc *scTestParams
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
steps []scStep
|
|
}{
|
|
{
|
|
name: "unknown event",
|
|
steps: []scStep{
|
|
{ // add P1
|
|
currentSc: &scTestParams{},
|
|
args: args{event: unknownEv{}},
|
|
wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")},
|
|
wantSc: &scTestParams{},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "single peer, sync 3 blocks",
|
|
steps: []scStep{
|
|
{ // add P1
|
|
currentSc: &scTestParams{startTime: now, peers: map[string]*scPeer{}, height: 1},
|
|
args: args{event: bcAddNewPeer{peerID: "P1"}},
|
|
wantEvent: noOpEvent{},
|
|
wantSc: &scTestParams{startTime: now, peers: map[string]*scPeer{
|
|
"P1": {base: -1, height: -1, state: peerStateNew}}, height: 1},
|
|
},
|
|
{ // set height of P1
|
|
args: args{event: bcStatusResponse{peerID: "P1", time: tick[0], height: 3}},
|
|
wantEvent: noOpEvent{},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3},
|
|
height: 1,
|
|
},
|
|
},
|
|
{ // schedule block 1
|
|
args: args{event: rTrySchedule{time: tick[1]}},
|
|
wantEvent: scBlockRequest{peerID: "P1", height: 1},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3},
|
|
pending: map[int64]p2p.NodeID{1: "P1"},
|
|
pendingTime: map[int64]time.Time{1: tick[1]},
|
|
height: 1,
|
|
},
|
|
},
|
|
{ // schedule block 2
|
|
args: args{event: rTrySchedule{time: tick[2]}},
|
|
wantEvent: scBlockRequest{peerID: "P1", height: 2},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P1"},
|
|
pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2]},
|
|
height: 1,
|
|
},
|
|
},
|
|
{ // schedule block 3
|
|
args: args{event: rTrySchedule{time: tick[3]}},
|
|
wantEvent: scBlockRequest{peerID: "P1", height: 3},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
|
|
allB: []int64{1, 2, 3},
|
|
pending: map[int64]p2p.NodeID{1: "P1", 2: "P1", 3: "P1"},
|
|
pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2], 3: tick[3]},
|
|
height: 1,
|
|
},
|
|
},
|
|
{ // block response 1
|
|
args: args{event: bcBlockResponse{peerID: "P1", time: tick[4], size: 100, block: block1}},
|
|
wantEvent: scBlockReceived{peerID: "P1", block: block1},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[4]}},
|
|
allB: []int64{1, 2, 3},
|
|
pending: map[int64]p2p.NodeID{2: "P1", 3: "P1"},
|
|
pendingTime: map[int64]time.Time{2: tick[2], 3: tick[3]},
|
|
received: map[int64]p2p.NodeID{1: "P1"},
|
|
height: 1,
|
|
},
|
|
},
|
|
{ // block response 2
|
|
args: args{event: bcBlockResponse{peerID: "P1", time: tick[5], size: 100, block: block2}},
|
|
wantEvent: scBlockReceived{peerID: "P1", block: block2},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[5]}},
|
|
allB: []int64{1, 2, 3},
|
|
pending: map[int64]p2p.NodeID{3: "P1"},
|
|
pendingTime: map[int64]time.Time{3: tick[3]},
|
|
received: map[int64]p2p.NodeID{1: "P1", 2: "P1"},
|
|
height: 1,
|
|
},
|
|
},
|
|
{ // block response 3
|
|
args: args{event: bcBlockResponse{peerID: "P1", time: tick[6], size: 100, block: block3}},
|
|
wantEvent: scBlockReceived{peerID: "P1", block: block3},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
|
|
allB: []int64{1, 2, 3},
|
|
received: map[int64]p2p.NodeID{1: "P1", 2: "P1", 3: "P1"},
|
|
height: 1,
|
|
},
|
|
},
|
|
{ // processed block 1
|
|
args: args{event: pcBlockProcessed{peerID: p2p.NodeID("P1"), height: 1}},
|
|
wantEvent: noOpEvent{},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
|
|
allB: []int64{2, 3},
|
|
received: map[int64]p2p.NodeID{2: "P1", 3: "P1"},
|
|
height: 2,
|
|
},
|
|
},
|
|
{ // processed block 2
|
|
args: args{event: pcBlockProcessed{peerID: p2p.NodeID("P1"), height: 2}},
|
|
wantEvent: scFinishedEv{},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
|
|
allB: []int64{3},
|
|
received: map[int64]p2p.NodeID{3: "P1"},
|
|
height: 3,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "block verification failure",
|
|
steps: []scStep{
|
|
{ // failure processing block 1
|
|
currentSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateReady, lastTouched: tick[6]},
|
|
"P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
|
|
allB: []int64{1, 2, 3, 4},
|
|
received: map[int64]p2p.NodeID{1: "P1", 2: "P1", 3: "P1"},
|
|
height: 1,
|
|
},
|
|
args: args{event: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P1"}},
|
|
wantEvent: noOpEvent{},
|
|
wantSc: &scTestParams{
|
|
startTime: now,
|
|
peers: map[string]*scPeer{
|
|
"P1": {height: 4, state: peerStateRemoved, lastTouched: tick[6]},
|
|
"P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
|
|
allB: []int64{1, 2, 3},
|
|
received: map[int64]p2p.NodeID{},
|
|
height: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var sc *scheduler
|
|
for i, step := range tt.steps {
|
|
// First step must always initialize the currentState as state.
|
|
if step.currentSc != nil {
|
|
sc = newTestScheduler(*step.currentSc)
|
|
}
|
|
if sc == nil {
|
|
panic("Bad (initial?) step")
|
|
}
|
|
|
|
nextEvent, err := sc.handle(step.args.event)
|
|
wantSc := newTestScheduler(*step.wantSc)
|
|
|
|
t.Logf("step %d(%v): %s", i, step.args.event, sc)
|
|
checkSameScheduler(t, wantSc, sc)
|
|
|
|
checkScResults(t, step.wantErr, err, step.wantEvent, nextEvent)
|
|
|
|
// Next step may use the wantedState as their currentState.
|
|
sc = newTestScheduler(*step.wantSc)
|
|
}
|
|
})
|
|
}
|
|
}
|