279 lines
7.1 KiB
Go
279 lines
7.1 KiB
Go
// stm: #unit
|
|
package chain
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/chain/types/mock"
|
|
)
|
|
|
|
func init() {
|
|
BootstrapPeerThreshold = 1
|
|
}
|
|
|
|
var genTs = mock.TipSet(mock.MkBlock(nil, 0, 0))
|
|
|
|
type syncOp struct {
|
|
ts *types.TipSet
|
|
done func()
|
|
}
|
|
|
|
func runSyncMgrTest(t *testing.T, tname string, thresh int, tf func(*testing.T, *syncManager, chan *syncOp)) {
|
|
syncTargets := make(chan *syncOp)
|
|
sm := NewSyncManager(func(ctx context.Context, ts *types.TipSet) error {
|
|
ch := make(chan struct{})
|
|
syncTargets <- &syncOp{
|
|
ts: ts,
|
|
done: func() { close(ch) },
|
|
}
|
|
<-ch
|
|
return nil
|
|
}).(*syncManager)
|
|
|
|
oldBootstrapPeerThreshold := BootstrapPeerThreshold
|
|
BootstrapPeerThreshold = thresh
|
|
defer func() {
|
|
BootstrapPeerThreshold = oldBootstrapPeerThreshold
|
|
}()
|
|
|
|
sm.Start()
|
|
defer sm.Stop()
|
|
t.Run(tname+fmt.Sprintf("-%d", thresh), func(t *testing.T) {
|
|
tf(t, sm, syncTargets)
|
|
})
|
|
}
|
|
|
|
func assertTsEqual(t *testing.T, actual, expected *types.TipSet) {
|
|
t.Helper()
|
|
if !actual.Equals(expected) {
|
|
t.Fatalf("got unexpected tipset %s (expected: %s)", actual.Cids(), expected.Cids())
|
|
}
|
|
}
|
|
|
|
func assertNoOp(t *testing.T, c chan *syncOp) {
|
|
t.Helper()
|
|
select {
|
|
case <-time.After(time.Millisecond * 20):
|
|
case <-c:
|
|
t.Fatal("shouldnt have gotten any sync operations yet")
|
|
}
|
|
}
|
|
|
|
func assertGetSyncOp(t *testing.T, c chan *syncOp, ts *types.TipSet) {
|
|
t.Helper()
|
|
|
|
select {
|
|
case <-time.After(time.Millisecond * 100):
|
|
t.Fatal("expected sync manager to try and sync to our target")
|
|
case op := <-c:
|
|
op.done()
|
|
if !op.ts.Equals(ts) {
|
|
t.Fatalf("somehow got wrong tipset from syncer (got %s, expected %s)", op.ts.Cids(), ts.Cids())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSyncManagerEdgeCase(t *testing.T) {
|
|
//stm: @CHAIN_SYNCER_SET_PEER_HEAD_001
|
|
ctx := context.Background()
|
|
|
|
a := mock.TipSet(mock.MkBlock(genTs, 1, 1))
|
|
t.Logf("a: %s", a)
|
|
b1 := mock.TipSet(mock.MkBlock(a, 1, 2))
|
|
t.Logf("b1: %s", b1)
|
|
b2 := mock.TipSet(mock.MkBlock(a, 2, 3))
|
|
t.Logf("b2: %s", b2)
|
|
c1 := mock.TipSet(mock.MkBlock(b1, 2, 4))
|
|
t.Logf("c1: %s", c1)
|
|
c2 := mock.TipSet(mock.MkBlock(b2, 1, 5))
|
|
t.Logf("c2: %s", c2)
|
|
d1 := mock.TipSet(mock.MkBlock(c1, 1, 6))
|
|
t.Logf("d1: %s", d1)
|
|
e1 := mock.TipSet(mock.MkBlock(d1, 1, 7))
|
|
t.Logf("e1: %s", e1)
|
|
|
|
runSyncMgrTest(t, "edgeCase", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) {
|
|
sm.SetPeerHead(ctx, "peer1", a)
|
|
|
|
sm.SetPeerHead(ctx, "peer1", b1)
|
|
sm.SetPeerHead(ctx, "peer1", b2)
|
|
|
|
assertGetSyncOp(t, stc, a)
|
|
|
|
// b1 and b2 are in queue after a; the sync manager should pick the heaviest one which is b2
|
|
bop := <-stc
|
|
if !bop.ts.Equals(b2) {
|
|
t.Fatalf("Expected tipset %s to sync, but got %s", b2, bop.ts)
|
|
}
|
|
|
|
sm.SetPeerHead(ctx, "peer2", c2)
|
|
sm.SetPeerHead(ctx, "peer2", c1)
|
|
sm.SetPeerHead(ctx, "peer3", b2)
|
|
sm.SetPeerHead(ctx, "peer1", a)
|
|
|
|
bop.done()
|
|
|
|
// get the next sync target; it should be c1 as the heaviest tipset but added last (same weight as c2)
|
|
bop = <-stc
|
|
if bop.ts.Equals(c2) {
|
|
// there's a small race and we might get c2 first.
|
|
// But we should still end on c1.
|
|
bop.done()
|
|
bop = <-stc
|
|
}
|
|
|
|
if !bop.ts.Equals(c1) {
|
|
t.Fatalf("Expected tipset %s to sync, but got %s", c1, bop.ts)
|
|
}
|
|
|
|
sm.SetPeerHead(ctx, "peer4", d1)
|
|
sm.SetPeerHead(ctx, "peer5", e1)
|
|
bop.done()
|
|
|
|
// get the last sync target; it should be e1
|
|
var last *types.TipSet
|
|
for i := 0; i < 10; {
|
|
select {
|
|
case bop = <-stc:
|
|
bop.done()
|
|
if last == nil || bop.ts.Height() > last.Height() {
|
|
last = bop.ts
|
|
}
|
|
default:
|
|
i++
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
if !last.Equals(e1) {
|
|
t.Fatalf("Expected tipset %s to sync, but got %s", e1, last)
|
|
}
|
|
|
|
sm.mx.Lock()
|
|
activeSyncs := len(sm.state)
|
|
sm.mx.Unlock()
|
|
if activeSyncs != 0 {
|
|
t.Errorf("active syncs expected empty but got: %d", activeSyncs)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSyncManager(t *testing.T) {
|
|
//stm: @CHAIN_SYNCER_SET_PEER_HEAD_001
|
|
ctx := context.Background()
|
|
|
|
a := mock.TipSet(mock.MkBlock(genTs, 1, 1))
|
|
b := mock.TipSet(mock.MkBlock(a, 1, 2))
|
|
c1 := mock.TipSet(mock.MkBlock(b, 1, 3))
|
|
c2 := mock.TipSet(mock.MkBlock(b, 2, 4))
|
|
c3 := mock.TipSet(mock.MkBlock(b, 3, 5))
|
|
d := mock.TipSet(mock.MkBlock(c1, 4, 5))
|
|
|
|
runSyncMgrTest(t, "testBootstrap", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) {
|
|
sm.SetPeerHead(ctx, "peer1", c1)
|
|
assertGetSyncOp(t, stc, c1)
|
|
})
|
|
|
|
runSyncMgrTest(t, "testBootstrap", 2, func(t *testing.T, sm *syncManager, stc chan *syncOp) {
|
|
sm.SetPeerHead(ctx, "peer1", c1)
|
|
assertNoOp(t, stc)
|
|
|
|
sm.SetPeerHead(ctx, "peer2", c1)
|
|
assertGetSyncOp(t, stc, c1)
|
|
})
|
|
|
|
runSyncMgrTest(t, "testSyncAfterBootstrap", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) {
|
|
sm.SetPeerHead(ctx, "peer1", b)
|
|
assertGetSyncOp(t, stc, b)
|
|
|
|
sm.SetPeerHead(ctx, "peer2", c1)
|
|
assertGetSyncOp(t, stc, c1)
|
|
|
|
sm.SetPeerHead(ctx, "peer2", c2)
|
|
assertGetSyncOp(t, stc, c2)
|
|
})
|
|
|
|
runSyncMgrTest(t, "testCoalescing", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) {
|
|
sm.SetPeerHead(ctx, "peer1", a)
|
|
assertGetSyncOp(t, stc, a)
|
|
|
|
sm.SetPeerHead(ctx, "peer2", b)
|
|
op := <-stc
|
|
|
|
sm.SetPeerHead(ctx, "peer2", c1)
|
|
sm.SetPeerHead(ctx, "peer2", c2)
|
|
sm.SetPeerHead(ctx, "peer2", d)
|
|
|
|
assertTsEqual(t, op.ts, b)
|
|
|
|
// need a better way to 'wait until syncmgr is idle'
|
|
time.Sleep(time.Millisecond * 20)
|
|
|
|
op.done()
|
|
|
|
assertGetSyncOp(t, stc, d)
|
|
})
|
|
|
|
runSyncMgrTest(t, "testSyncIncomingTipset", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) {
|
|
sm.SetPeerHead(ctx, "peer1", a)
|
|
assertGetSyncOp(t, stc, a)
|
|
|
|
sm.SetPeerHead(ctx, "peer2", b)
|
|
op := <-stc
|
|
op.done()
|
|
|
|
sm.SetPeerHead(ctx, "peer2", c1)
|
|
op1 := <-stc
|
|
fmt.Println("op1: ", op1.ts.Cids())
|
|
|
|
sm.SetPeerHead(ctx, "peer2", c2)
|
|
sm.SetPeerHead(ctx, "peer2", c3)
|
|
|
|
op1.done()
|
|
|
|
op2 := <-stc
|
|
fmt.Println("op2: ", op2.ts.Cids())
|
|
op2.done()
|
|
|
|
op3 := <-stc
|
|
fmt.Println("op3: ", op3.ts.Cids())
|
|
op3.done()
|
|
})
|
|
}
|
|
|
|
func TestSyncManagerBucketSet(t *testing.T) {
|
|
ts1 := mock.TipSet(mock.MkBlock(nil, 0, 0))
|
|
ts2 := mock.TipSet(mock.MkBlock(ts1, 1, 0))
|
|
bucket1 := newSyncTargetBucket(ts1, ts2)
|
|
bucketSet := syncBucketSet{buckets: []*syncTargetBucket{bucket1}}
|
|
|
|
// inserting a tipset (potential sync target) from an existing chain, should add to an existing bucket
|
|
//stm: @CHAIN_SYNCER_ADD_SYNC_TARGET_001
|
|
ts3 := mock.TipSet(mock.MkBlock(ts2, 2, 0))
|
|
bucketSet.Insert(ts3)
|
|
require.Equal(t, 1, len(bucketSet.buckets))
|
|
require.Equal(t, 3, len(bucketSet.buckets[0].tips))
|
|
|
|
// inserting a tipset from new chain, should create a new bucket
|
|
ts4fork := mock.TipSet(mock.MkBlock(nil, 1, 1))
|
|
bucketSet.Insert(ts4fork)
|
|
require.Equal(t, 2, len(bucketSet.buckets))
|
|
require.Equal(t, 3, len(bucketSet.buckets[0].tips))
|
|
require.Equal(t, 1, len(bucketSet.buckets[1].tips))
|
|
|
|
// Pop removes the best bucket (best sync target), e.g. bucket1
|
|
//stm: @CHAIN_SYNCER_SELECT_SYNC_TARGET_001
|
|
popped := bucketSet.Pop()
|
|
require.Equal(t, popped, bucket1)
|
|
require.Equal(t, 1, len(bucketSet.buckets))
|
|
|
|
// PopRelated removes the bucket containing the given tipset, leaving the set empty
|
|
bucketSet.PopRelated(ts4fork)
|
|
require.Equal(t, 0, len(bucketSet.buckets))
|
|
}
|