You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

304 lines
7.2 KiB

package statesync
import (
"context"
"fmt"
"strings"
"sync"
"testing"
"time"
"github.com/fortytw2/leaktest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/internal/p2p"
ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
"github.com/tendermint/tendermint/types"
)
func TestDispatcherBasic(t *testing.T) {
t.Cleanup(leaktest.Check(t))
ch := make(chan p2p.Envelope, 100)
closeCh := make(chan struct{})
defer close(closeCh)
d := newDispatcher(ch, 1*time.Second)
go handleRequests(t, d, ch, closeCh)
peers := createPeerSet(5)
for _, peer := range peers {
d.addPeer(peer)
}
wg := sync.WaitGroup{}
// make a bunch of async requests and require that the correct responses are
// given
for i := 1; i < 10; i++ {
wg.Add(1)
go func(height int64) {
defer wg.Done()
lb, peer, err := d.LightBlock(context.Background(), height)
require.NoError(t, err)
require.NotNil(t, lb)
require.Equal(t, lb.Height, height)
require.Contains(t, peers, peer)
}(int64(i))
}
wg.Wait()
}
func TestDispatcherReturnsNoBlock(t *testing.T) {
t.Cleanup(leaktest.Check(t))
ch := make(chan p2p.Envelope, 100)
d := newDispatcher(ch, 1*time.Second)
peerFromSet := createPeerSet(1)[0]
d.addPeer(peerFromSet)
doneCh := make(chan struct{})
go func() {
<-ch
require.NoError(t, d.respond(nil, peerFromSet))
close(doneCh)
}()
lb, peerResult, err := d.LightBlock(context.Background(), 1)
<-doneCh
require.Nil(t, lb)
require.Nil(t, err)
require.Equal(t, peerFromSet, peerResult)
}
func TestDispatcherErrorsWhenNoPeers(t *testing.T) {
t.Cleanup(leaktest.Check(t))
ch := make(chan p2p.Envelope, 100)
d := newDispatcher(ch, 1*time.Second)
lb, peerResult, err := d.LightBlock(context.Background(), 1)
require.Nil(t, lb)
require.Empty(t, peerResult)
require.Equal(t, errNoConnectedPeers, err)
}
func TestDispatcherReturnsBlockOncePeerAvailable(t *testing.T) {
t.Cleanup(leaktest.Check(t))
dispatcherRequestCh := make(chan p2p.Envelope, 100)
d := newDispatcher(dispatcherRequestCh, 1*time.Second)
peerFromSet := createPeerSet(1)[0]
d.addPeer(peerFromSet)
ctx := context.Background()
wrapped, cancelFunc := context.WithCancel(ctx)
doneCh := make(chan struct{})
go func() {
lb, peerResult, err := d.LightBlock(wrapped, 1)
require.Nil(t, lb)
require.Equal(t, peerFromSet, peerResult)
require.Nil(t, err)
// calls to dispatcher.Lightblock write into the dispatcher's requestCh.
// we read from the requestCh here to unblock the requestCh for future
// calls.
<-dispatcherRequestCh
close(doneCh)
}()
cancelFunc()
<-doneCh
go func() {
<-dispatcherRequestCh
lb := &types.LightBlock{}
asProto, err := lb.ToProto()
require.Nil(t, err)
err = d.respond(asProto, peerFromSet)
require.Nil(t, err)
}()
lb, peerResult, err := d.LightBlock(context.Background(), 1)
require.NotNil(t, lb)
require.Equal(t, peerFromSet, peerResult)
require.Nil(t, err)
}
func TestDispatcherProviders(t *testing.T) {
t.Cleanup(leaktest.Check(t))
ch := make(chan p2p.Envelope, 100)
chainID := "state-sync-test"
closeCh := make(chan struct{})
defer close(closeCh)
d := newDispatcher(ch, 1*time.Second)
go handleRequests(t, d, ch, closeCh)
peers := createPeerSet(5)
for _, peer := range peers {
d.addPeer(peer)
}
providers := d.Providers(chainID, 5*time.Second)
require.Len(t, providers, 5)
for i, p := range providers {
bp, ok := p.(*blockProvider)
require.True(t, ok)
assert.Equal(t, bp.String(), string(peers[i]))
lb, err := p.LightBlock(context.Background(), 10)
assert.Error(t, err)
assert.Nil(t, lb)
}
}
func TestPeerListBasic(t *testing.T) {
t.Cleanup(leaktest.Check(t))
peerList := newPeerList()
assert.Zero(t, peerList.Len())
numPeers := 10
peerSet := createPeerSet(numPeers)
for _, peer := range peerSet {
peerList.Append(peer)
}
for idx, peer := range peerList.Peers() {
assert.Equal(t, peer, peerSet[idx])
}
assert.Equal(t, numPeers, peerList.Len())
half := numPeers / 2
for i := 0; i < half; i++ {
assert.Equal(t, peerSet[i], peerList.Pop(ctx))
}
assert.Equal(t, half, peerList.Len())
peerList.Remove(types.NodeID("lp"))
assert.Equal(t, half, peerList.Len())
peerList.Remove(peerSet[half])
half++
assert.Equal(t, peerSet[half], peerList.Pop(ctx))
}
func TestPeerListBlocksWhenEmpty(t *testing.T) {
t.Cleanup(leaktest.Check(t))
peerList := newPeerList()
require.Zero(t, peerList.Len())
doneCh := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
peerList.Pop(ctx)
close(doneCh)
}()
select {
case <-doneCh:
t.Error("empty peer list should not have returned result")
case <-time.After(100 * time.Millisecond):
}
}
func TestEmptyPeerListReturnsWhenContextCanceled(t *testing.T) {
t.Cleanup(leaktest.Check(t))
peerList := newPeerList()
require.Zero(t, peerList.Len())
doneCh := make(chan struct{})
ctx := context.Background()
wrapped, cancel := context.WithCancel(ctx)
go func() {
peerList.Pop(wrapped)
close(doneCh)
}()
select {
case <-doneCh:
t.Error("empty peer list should not have returned result")
case <-time.After(100 * time.Millisecond):
}
cancel()
select {
case <-doneCh:
case <-time.After(100 * time.Millisecond):
t.Error("peer list should have returned after context canceled")
}
}
func TestPeerListConcurrent(t *testing.T) {
t.Cleanup(leaktest.Check(t))
peerList := newPeerList()
numPeers := 10
wg := sync.WaitGroup{}
// we run a set of goroutines requesting the next peer in the list. As the
// peer list hasn't been populated each these go routines should block
for i := 0; i < numPeers/2; i++ {
go func() {
_ = peerList.Pop(ctx)
wg.Done()
}()
}
// now we add the peers to the list, this should allow the previously
// blocked go routines to unblock
for _, peer := range createPeerSet(numPeers) {
wg.Add(1)
peerList.Append(peer)
}
// we request the second half of the peer set
for i := 0; i < numPeers/2; i++ {
go func() {
_ = peerList.Pop(ctx)
wg.Done()
}()
}
// we use a context with cancel and a separate go routine to wait for all
// the other goroutines to close.
ctx, cancel := context.WithCancel(context.Background())
go func() { wg.Wait(); cancel() }()
select {
case <-time.After(time.Second):
// not all of the blocked go routines waiting on peers have closed after
// one second. This likely means the list got blocked.
t.Failed()
case <-ctx.Done():
// there should be no peers remaining
require.Equal(t, 0, peerList.Len())
}
}
// handleRequests is a helper function usually run in a separate go routine to
// imitate the expected responses of the reactor wired to the dispatcher
func handleRequests(t *testing.T, d *dispatcher, ch chan p2p.Envelope, closeCh chan struct{}) {
t.Helper()
for {
select {
case request := <-ch:
height := request.Message.(*ssproto.LightBlockRequest).Height
peer := request.To
resp := mockLBResp(t, peer, int64(height), time.Now())
block, _ := resp.block.ToProto()
require.NoError(t, d.respond(block, resp.peer))
case <-closeCh:
return
}
}
}
func createPeerSet(num int) []types.NodeID {
peers := make([]types.NodeID, num)
for i := 0; i < num; i++ {
peers[i], _ = types.NewNodeID(strings.Repeat(fmt.Sprintf("%d", i), 2*types.NodeIDByteLength))
}
return peers
}