github.com/vipernet-xyz/tm@v0.34.24/statesync/reactor_test.go (about)

     1  package statesync
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/gogo/protobuf/proto"
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/mock"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	abci "github.com/vipernet-xyz/tm/abci/types"
    13  	"github.com/vipernet-xyz/tm/config"
    14  	"github.com/vipernet-xyz/tm/p2p"
    15  	p2pmocks "github.com/vipernet-xyz/tm/p2p/mocks"
    16  	ssproto "github.com/vipernet-xyz/tm/proto/tendermint/statesync"
    17  	proxymocks "github.com/vipernet-xyz/tm/proxy/mocks"
    18  )
    19  
    20  func TestReactor_Receive_ChunkRequest(t *testing.T) {
    21  	testcases := map[string]struct {
    22  		request        *ssproto.ChunkRequest
    23  		chunk          []byte
    24  		expectResponse *ssproto.ChunkResponse
    25  	}{
    26  		"chunk is returned": {
    27  			&ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
    28  			[]byte{1, 2, 3},
    29  			&ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Chunk: []byte{1, 2, 3}}},
    30  		"empty chunk is returned, as nil": {
    31  			&ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
    32  			[]byte{},
    33  			&ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Chunk: nil}},
    34  		"nil (missing) chunk is returned as missing": {
    35  			&ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
    36  			nil,
    37  			&ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Missing: true},
    38  		},
    39  	}
    40  
    41  	for name, tc := range testcases {
    42  		tc := tc
    43  		t.Run(name, func(t *testing.T) {
    44  			// Mock ABCI connection to return local snapshots
    45  			conn := &proxymocks.AppConnSnapshot{}
    46  			conn.On("LoadSnapshotChunkSync", abci.RequestLoadSnapshotChunk{
    47  				Height: tc.request.Height,
    48  				Format: tc.request.Format,
    49  				Chunk:  tc.request.Index,
    50  			}).Return(&abci.ResponseLoadSnapshotChunk{Chunk: tc.chunk}, nil)
    51  
    52  			// Mock peer to store response, if found
    53  			peer := &p2pmocks.Peer{}
    54  			peer.On("ID").Return(p2p.ID("id"))
    55  			var response *ssproto.ChunkResponse
    56  			if tc.expectResponse != nil {
    57  				peer.On("SendEnvelope", mock.MatchedBy(func(i interface{}) bool {
    58  					e, ok := i.(p2p.Envelope)
    59  					return ok && e.ChannelID == ChunkChannel
    60  				})).Run(func(args mock.Arguments) {
    61  					e := args[0].(p2p.Envelope)
    62  
    63  					// Marshal to simulate a wire roundtrip.
    64  					bz, err := proto.Marshal(e.Message)
    65  					require.NoError(t, err)
    66  					err = proto.Unmarshal(bz, e.Message)
    67  					require.NoError(t, err)
    68  					response = e.Message.(*ssproto.ChunkResponse)
    69  				}).Return(true)
    70  			}
    71  
    72  			// Start a reactor and send a ssproto.ChunkRequest, then wait for and check response
    73  			cfg := config.DefaultStateSyncConfig()
    74  			r := NewReactor(*cfg, conn, nil, "")
    75  			err := r.Start()
    76  			require.NoError(t, err)
    77  			t.Cleanup(func() {
    78  				if err := r.Stop(); err != nil {
    79  					t.Error(err)
    80  				}
    81  			})
    82  
    83  			r.ReceiveEnvelope(p2p.Envelope{
    84  				ChannelID: ChunkChannel,
    85  				Src:       peer,
    86  				Message:   tc.request,
    87  			})
    88  			time.Sleep(100 * time.Millisecond)
    89  			assert.Equal(t, tc.expectResponse, response)
    90  
    91  			conn.AssertExpectations(t)
    92  			peer.AssertExpectations(t)
    93  		})
    94  	}
    95  }
    96  
    97  func TestReactor_Receive_SnapshotsRequest(t *testing.T) {
    98  	testcases := map[string]struct {
    99  		snapshots       []*abci.Snapshot
   100  		expectResponses []*ssproto.SnapshotsResponse
   101  	}{
   102  		"no snapshots": {nil, []*ssproto.SnapshotsResponse{}},
   103  		">10 unordered snapshots": {
   104  			[]*abci.Snapshot{
   105  				{Height: 1, Format: 2, Chunks: 7, Hash: []byte{1, 2}, Metadata: []byte{1}},
   106  				{Height: 2, Format: 2, Chunks: 7, Hash: []byte{2, 2}, Metadata: []byte{2}},
   107  				{Height: 3, Format: 2, Chunks: 7, Hash: []byte{3, 2}, Metadata: []byte{3}},
   108  				{Height: 1, Format: 1, Chunks: 7, Hash: []byte{1, 1}, Metadata: []byte{4}},
   109  				{Height: 2, Format: 1, Chunks: 7, Hash: []byte{2, 1}, Metadata: []byte{5}},
   110  				{Height: 3, Format: 1, Chunks: 7, Hash: []byte{3, 1}, Metadata: []byte{6}},
   111  				{Height: 1, Format: 4, Chunks: 7, Hash: []byte{1, 4}, Metadata: []byte{7}},
   112  				{Height: 2, Format: 4, Chunks: 7, Hash: []byte{2, 4}, Metadata: []byte{8}},
   113  				{Height: 3, Format: 4, Chunks: 7, Hash: []byte{3, 4}, Metadata: []byte{9}},
   114  				{Height: 1, Format: 3, Chunks: 7, Hash: []byte{1, 3}, Metadata: []byte{10}},
   115  				{Height: 2, Format: 3, Chunks: 7, Hash: []byte{2, 3}, Metadata: []byte{11}},
   116  				{Height: 3, Format: 3, Chunks: 7, Hash: []byte{3, 3}, Metadata: []byte{12}},
   117  			},
   118  			[]*ssproto.SnapshotsResponse{
   119  				{Height: 3, Format: 4, Chunks: 7, Hash: []byte{3, 4}, Metadata: []byte{9}},
   120  				{Height: 3, Format: 3, Chunks: 7, Hash: []byte{3, 3}, Metadata: []byte{12}},
   121  				{Height: 3, Format: 2, Chunks: 7, Hash: []byte{3, 2}, Metadata: []byte{3}},
   122  				{Height: 3, Format: 1, Chunks: 7, Hash: []byte{3, 1}, Metadata: []byte{6}},
   123  				{Height: 2, Format: 4, Chunks: 7, Hash: []byte{2, 4}, Metadata: []byte{8}},
   124  				{Height: 2, Format: 3, Chunks: 7, Hash: []byte{2, 3}, Metadata: []byte{11}},
   125  				{Height: 2, Format: 2, Chunks: 7, Hash: []byte{2, 2}, Metadata: []byte{2}},
   126  				{Height: 2, Format: 1, Chunks: 7, Hash: []byte{2, 1}, Metadata: []byte{5}},
   127  				{Height: 1, Format: 4, Chunks: 7, Hash: []byte{1, 4}, Metadata: []byte{7}},
   128  				{Height: 1, Format: 3, Chunks: 7, Hash: []byte{1, 3}, Metadata: []byte{10}},
   129  			},
   130  		},
   131  	}
   132  
   133  	for name, tc := range testcases {
   134  		tc := tc
   135  		t.Run(name, func(t *testing.T) {
   136  			// Mock ABCI connection to return local snapshots
   137  			conn := &proxymocks.AppConnSnapshot{}
   138  			conn.On("ListSnapshotsSync", abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{
   139  				Snapshots: tc.snapshots,
   140  			}, nil)
   141  
   142  			// Mock peer to catch responses and store them in a slice
   143  			responses := []*ssproto.SnapshotsResponse{}
   144  			peer := &p2pmocks.Peer{}
   145  			if len(tc.expectResponses) > 0 {
   146  				peer.On("ID").Return(p2p.ID("id"))
   147  				peer.On("SendEnvelope", mock.MatchedBy(func(i interface{}) bool {
   148  					e, ok := i.(p2p.Envelope)
   149  					return ok && e.ChannelID == SnapshotChannel
   150  				})).Run(func(args mock.Arguments) {
   151  					e := args[0].(p2p.Envelope)
   152  
   153  					// Marshal to simulate a wire roundtrip.
   154  					bz, err := proto.Marshal(e.Message)
   155  					require.NoError(t, err)
   156  					err = proto.Unmarshal(bz, e.Message)
   157  					require.NoError(t, err)
   158  					responses = append(responses, e.Message.(*ssproto.SnapshotsResponse))
   159  				}).Return(true)
   160  			}
   161  
   162  			// Start a reactor and send a SnapshotsRequestMessage, then wait for and check responses
   163  			cfg := config.DefaultStateSyncConfig()
   164  			r := NewReactor(*cfg, conn, nil, "")
   165  			err := r.Start()
   166  			require.NoError(t, err)
   167  			t.Cleanup(func() {
   168  				if err := r.Stop(); err != nil {
   169  					t.Error(err)
   170  				}
   171  			})
   172  
   173  			r.ReceiveEnvelope(p2p.Envelope{
   174  				ChannelID: SnapshotChannel,
   175  				Src:       peer,
   176  				Message:   &ssproto.SnapshotsRequest{},
   177  			})
   178  			time.Sleep(100 * time.Millisecond)
   179  			assert.Equal(t, tc.expectResponses, responses)
   180  
   181  			conn.AssertExpectations(t)
   182  			peer.AssertExpectations(t)
   183  		})
   184  	}
   185  }
   186  
   187  func TestLegacyReactorReceiveBasic(t *testing.T) {
   188  	cfg := config.DefaultStateSyncConfig()
   189  	conn := &proxymocks.AppConnSnapshot{}
   190  	reactor := NewReactor(*cfg, conn, nil, "")
   191  	peer := p2p.CreateRandomPeer(false)
   192  
   193  	reactor.InitPeer(peer)
   194  	reactor.AddPeer(peer)
   195  	m := &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1}
   196  	wm := m.Wrap()
   197  	msg, err := proto.Marshal(wm)
   198  	assert.NoError(t, err)
   199  
   200  	assert.NotPanics(t, func() {
   201  		reactor.Receive(ChunkChannel, peer, msg)
   202  	})
   203  }