github.com/Finschia/ostracon@v1.1.5/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/tendermint/tendermint/abci/types"
    13  	tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
    14  	ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
    15  	tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
    16  
    17  	"github.com/Finschia/ostracon/config"
    18  	"github.com/Finschia/ostracon/libs/log"
    19  	"github.com/Finschia/ostracon/p2p"
    20  	p2pmocks "github.com/Finschia/ostracon/p2p/mocks"
    21  	"github.com/Finschia/ostracon/proxy"
    22  	proxymocks "github.com/Finschia/ostracon/proxy/mocks"
    23  	sm "github.com/Finschia/ostracon/state"
    24  	"github.com/Finschia/ostracon/statesync/mocks"
    25  	"github.com/Finschia/ostracon/types"
    26  	"github.com/Finschia/ostracon/version"
    27  )
    28  
    29  type Peer struct {
    30  	*p2pmocks.Peer
    31  	*p2pmocks.EnvelopeSender
    32  }
    33  
    34  func TestReactor_Receive_ChunkRequest(t *testing.T) {
    35  	testcases := map[string]struct {
    36  		request        *ssproto.ChunkRequest
    37  		chunk          []byte
    38  		expectResponse *ssproto.ChunkResponse
    39  	}{
    40  		"chunk is returned": {
    41  			&ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
    42  			[]byte{1, 2, 3},
    43  			&ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Chunk: []byte{1, 2, 3}}},
    44  		"empty chunk is returned, as nil": {
    45  			&ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
    46  			[]byte{},
    47  			&ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Chunk: nil}},
    48  		"nil (missing) chunk is returned as missing": {
    49  			&ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1},
    50  			nil,
    51  			&ssproto.ChunkResponse{Height: 1, Format: 1, Index: 1, Missing: true},
    52  		},
    53  	}
    54  
    55  	for name, tc := range testcases {
    56  		tc := tc
    57  		t.Run(name, func(t *testing.T) {
    58  			// Mock ABCI connection to return local snapshots
    59  			conn := &proxymocks.AppConnSnapshot{}
    60  			conn.On("LoadSnapshotChunkSync", abci.RequestLoadSnapshotChunk{
    61  				Height: tc.request.Height,
    62  				Format: tc.request.Format,
    63  				Chunk:  tc.request.Index,
    64  			}).Return(&abci.ResponseLoadSnapshotChunk{Chunk: tc.chunk}, nil)
    65  
    66  			// Mock peer to store response, if found
    67  			peer := &Peer{Peer: &p2pmocks.Peer{}, EnvelopeSender: &p2pmocks.EnvelopeSender{}}
    68  			peer.Peer.On("ID").Return(p2p.ID("id"))
    69  			var response *ssproto.ChunkResponse
    70  			if tc.expectResponse != nil {
    71  				peer.EnvelopeSender.On("SendEnvelope", mock.MatchedBy(func(i interface{}) bool {
    72  					e, ok := i.(p2p.Envelope)
    73  					return ok && e.ChannelID == ChunkChannel
    74  				})).Run(func(args mock.Arguments) {
    75  					e := args[0].(p2p.Envelope)
    76  
    77  					// Marshal to simulate a wire roundtrip.
    78  					bz, err := proto.Marshal(e.Message)
    79  					require.NoError(t, err)
    80  					err = proto.Unmarshal(bz, e.Message)
    81  					require.NoError(t, err)
    82  					response = e.Message.(*ssproto.ChunkResponse)
    83  				}).Return(true)
    84  			}
    85  
    86  			// Start a reactor and send a ssproto.ChunkRequest, then wait for and check response
    87  			cfg := config.DefaultStateSyncConfig()
    88  			r := NewReactor(*cfg, conn, nil, true, 1000)
    89  			err := r.Start()
    90  			require.NoError(t, err)
    91  			t.Cleanup(func() {
    92  				if err := r.Stop(); err != nil {
    93  					t.Error(err)
    94  				}
    95  			})
    96  
    97  			r.ReceiveEnvelope(p2p.Envelope{
    98  				ChannelID: ChunkChannel,
    99  				Src:       peer,
   100  				Message:   tc.request,
   101  			})
   102  			time.Sleep(100 * time.Millisecond)
   103  			assert.Equal(t, tc.expectResponse, response)
   104  
   105  			conn.AssertExpectations(t)
   106  			peer.Peer.AssertExpectations(t)
   107  			peer.EnvelopeSender.AssertExpectations(t)
   108  		})
   109  	}
   110  }
   111  
   112  func TestReactor_Receive_SnapshotsRequest(t *testing.T) {
   113  	testcases := map[string]struct {
   114  		snapshots       []*abci.Snapshot
   115  		expectResponses []*ssproto.SnapshotsResponse
   116  	}{
   117  		"no snapshots": {nil, []*ssproto.SnapshotsResponse{}},
   118  		">10 unordered snapshots": {
   119  			[]*abci.Snapshot{
   120  				{Height: 1, Format: 2, Chunks: 7, Hash: []byte{1, 2}, Metadata: []byte{1}},
   121  				{Height: 2, Format: 2, Chunks: 7, Hash: []byte{2, 2}, Metadata: []byte{2}},
   122  				{Height: 3, Format: 2, Chunks: 7, Hash: []byte{3, 2}, Metadata: []byte{3}},
   123  				{Height: 1, Format: 1, Chunks: 7, Hash: []byte{1, 1}, Metadata: []byte{4}},
   124  				{Height: 2, Format: 1, Chunks: 7, Hash: []byte{2, 1}, Metadata: []byte{5}},
   125  				{Height: 3, Format: 1, Chunks: 7, Hash: []byte{3, 1}, Metadata: []byte{6}},
   126  				{Height: 1, Format: 4, Chunks: 7, Hash: []byte{1, 4}, Metadata: []byte{7}},
   127  				{Height: 2, Format: 4, Chunks: 7, Hash: []byte{2, 4}, Metadata: []byte{8}},
   128  				{Height: 3, Format: 4, Chunks: 7, Hash: []byte{3, 4}, Metadata: []byte{9}},
   129  				{Height: 1, Format: 3, Chunks: 7, Hash: []byte{1, 3}, Metadata: []byte{10}},
   130  				{Height: 2, Format: 3, Chunks: 7, Hash: []byte{2, 3}, Metadata: []byte{11}},
   131  				{Height: 3, Format: 3, Chunks: 7, Hash: []byte{3, 3}, Metadata: []byte{12}},
   132  			},
   133  			[]*ssproto.SnapshotsResponse{
   134  				{Height: 3, Format: 4, Chunks: 7, Hash: []byte{3, 4}, Metadata: []byte{9}},
   135  				{Height: 3, Format: 3, Chunks: 7, Hash: []byte{3, 3}, Metadata: []byte{12}},
   136  				{Height: 3, Format: 2, Chunks: 7, Hash: []byte{3, 2}, Metadata: []byte{3}},
   137  				{Height: 3, Format: 1, Chunks: 7, Hash: []byte{3, 1}, Metadata: []byte{6}},
   138  				{Height: 2, Format: 4, Chunks: 7, Hash: []byte{2, 4}, Metadata: []byte{8}},
   139  				{Height: 2, Format: 3, Chunks: 7, Hash: []byte{2, 3}, Metadata: []byte{11}},
   140  				{Height: 2, Format: 2, Chunks: 7, Hash: []byte{2, 2}, Metadata: []byte{2}},
   141  				{Height: 2, Format: 1, Chunks: 7, Hash: []byte{2, 1}, Metadata: []byte{5}},
   142  				{Height: 1, Format: 4, Chunks: 7, Hash: []byte{1, 4}, Metadata: []byte{7}},
   143  				{Height: 1, Format: 3, Chunks: 7, Hash: []byte{1, 3}, Metadata: []byte{10}},
   144  			},
   145  		},
   146  	}
   147  
   148  	for name, tc := range testcases {
   149  		tc := tc
   150  		t.Run(name, func(t *testing.T) {
   151  			// Mock ABCI connection to return local snapshots
   152  			conn := &proxymocks.AppConnSnapshot{}
   153  			conn.On("ListSnapshotsSync", abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{
   154  				Snapshots: tc.snapshots,
   155  			}, nil)
   156  
   157  			// Mock peer to catch responses and store them in a slice
   158  			responses := []*ssproto.SnapshotsResponse{}
   159  			peer := &Peer{Peer: &p2pmocks.Peer{}, EnvelopeSender: &p2pmocks.EnvelopeSender{}}
   160  			if len(tc.expectResponses) > 0 {
   161  				peer.Peer.On("ID").Return(p2p.ID("id"))
   162  				peer.EnvelopeSender.On("SendEnvelope", mock.MatchedBy(func(i interface{}) bool {
   163  					e, ok := i.(p2p.Envelope)
   164  					return ok && e.ChannelID == SnapshotChannel
   165  				})).Run(func(args mock.Arguments) {
   166  					e := args[0].(p2p.Envelope)
   167  
   168  					// Marshal to simulate a wire roundtrip.
   169  					bz, err := proto.Marshal(e.Message)
   170  					require.NoError(t, err)
   171  					err = proto.Unmarshal(bz, e.Message)
   172  					require.NoError(t, err)
   173  					responses = append(responses, e.Message.(*ssproto.SnapshotsResponse))
   174  				}).Return(true)
   175  			}
   176  
   177  			// Start a reactor and send a SnapshotsRequestMessage, then wait for and check responses
   178  			cfg := config.DefaultStateSyncConfig()
   179  			r := NewReactor(*cfg, conn, nil, true, 1000)
   180  			err := r.Start()
   181  			require.NoError(t, err)
   182  			t.Cleanup(func() {
   183  				if err := r.Stop(); err != nil {
   184  					t.Error(err)
   185  				}
   186  			})
   187  
   188  			r.ReceiveEnvelope(p2p.Envelope{
   189  				ChannelID: SnapshotChannel,
   190  				Src:       peer,
   191  				Message:   &ssproto.SnapshotsRequest{},
   192  			})
   193  			time.Sleep(100 * time.Millisecond)
   194  			assert.Equal(t, tc.expectResponses, responses)
   195  
   196  			conn.AssertExpectations(t)
   197  			peer.Peer.AssertExpectations(t)
   198  			peer.EnvelopeSender.AssertExpectations(t)
   199  		})
   200  	}
   201  }
   202  
   203  func TestLegacyReactorReceiveBasic(t *testing.T) {
   204  	cfg := config.DefaultStateSyncConfig()
   205  	conn := &proxymocks.AppConnSnapshot{}
   206  	reactor := NewReactor(*cfg, conn, nil, true, 1000)
   207  	peer := p2p.CreateRandomPeer(false)
   208  
   209  	reactor.InitPeer(peer)
   210  	reactor.AddPeer(peer)
   211  	m := &ssproto.ChunkRequest{Height: 1, Format: 1, Index: 1}
   212  	wm := m.Wrap()
   213  	msg, err := proto.Marshal(wm)
   214  	assert.NoError(t, err)
   215  
   216  	assert.NotPanics(t, func() {
   217  		reactor.Receive(ChunkChannel, peer, msg)
   218  	})
   219  }
   220  
   221  func makeTestStateSyncReactor(
   222  	t *testing.T, appHash string, height int64, snapshot *snapshot, chunks []*chunk) *Reactor {
   223  	connSnapshot := makeMockAppConnSnapshot(appHash, snapshot, chunks)
   224  	connQuery := makeMockAppConnQuery(appHash, height)
   225  
   226  	p2pConfig := config.DefaultP2PConfig()
   227  	p2pConfig.PexReactor = true
   228  	p2pConfig.AllowDuplicateIP = true
   229  
   230  	name := "STATE_SYNC_REACTOR_FOR_TEST"
   231  	size := 2
   232  	reactors := make([]*Reactor, size)
   233  	initSwitch := func(i int, s *p2p.Switch, p2pConfig *config.P2PConfig) *p2p.Switch {
   234  		logger := log.TestingLogger()
   235  		cfg := config.DefaultStateSyncConfig()
   236  		reactors[i] = NewReactor(*cfg, connSnapshot, connQuery, true, 1000)
   237  		reactors[i].SetLogger(logger)
   238  		reactors[i].SetSwitch(s)
   239  
   240  		s.AddReactor(name, reactors[i])
   241  		s.SetLogger(logger)
   242  		return s
   243  	}
   244  	switches := p2p.MakeConnectedSwitches(p2pConfig, size, initSwitch, p2p.Connect2Switches)
   245  
   246  	t.Cleanup(func() {
   247  		for i := 0; i < size; i++ {
   248  			if err := switches[i].Stop(); err != nil {
   249  				t.Error(err)
   250  			}
   251  		}
   252  	})
   253  
   254  	return reactors[0]
   255  }
   256  
   257  func makeMockAppConnSnapshot(appHash string, snapshot *snapshot, chunks []*chunk) *proxymocks.AppConnSnapshot {
   258  	connSnapshot := &proxymocks.AppConnSnapshot{}
   259  
   260  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   261  		Snapshot: &abci.Snapshot{
   262  			Height: snapshot.Height,
   263  			Format: snapshot.Format,
   264  			Chunks: snapshot.Chunks,
   265  			Hash:   snapshot.Hash,
   266  		},
   267  		AppHash: []byte(appHash),
   268  	}).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil)
   269  
   270  	connSnapshot.On("ListSnapshotsSync", abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{
   271  		Snapshots: []*abci.Snapshot{{
   272  			Height:   snapshot.Height,
   273  			Format:   snapshot.Format,
   274  			Chunks:   snapshot.Chunks,
   275  			Hash:     snapshot.Hash,
   276  			Metadata: snapshot.Metadata,
   277  		}},
   278  	}, nil)
   279  
   280  	index := len(chunks)
   281  	for i := 0; i < index; i++ {
   282  		connSnapshot.On("ApplySnapshotChunkSync",
   283  			mock.Anything).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   284  		connSnapshot.On("LoadSnapshotChunkSync", abci.RequestLoadSnapshotChunk{
   285  			Height: chunks[i].Height, Format: chunks[i].Format, Chunk: chunks[i].Index,
   286  		}).Return(&abci.ResponseLoadSnapshotChunk{Chunk: chunks[i].Chunk}, nil)
   287  	}
   288  
   289  	return connSnapshot
   290  }
   291  
   292  func makeMockAppConnQuery(appHash string, height int64) *proxymocks.AppConnQuery {
   293  	connQuery := &proxymocks.AppConnQuery{}
   294  	connQuery.On("InfoSync", proxy.RequestInfo).Return(&abci.ResponseInfo{
   295  		AppVersion:       testAppVersion,
   296  		LastBlockHeight:  height,
   297  		LastBlockAppHash: []byte(appHash),
   298  	}, nil)
   299  	return connQuery
   300  }
   301  
   302  func makeTestStateAndCommit(appHash string, height int64) (sm.State, *types.Commit) {
   303  	blockHash := "block_hash"
   304  
   305  	state := sm.State{
   306  		ChainID: "chain",
   307  		Version: tmstate.Version{
   308  			Consensus: tmversion.Consensus{
   309  				Block: version.BlockProtocol,
   310  				App:   testAppVersion,
   311  			},
   312  
   313  			Software: version.OCCoreSemVer,
   314  		},
   315  
   316  		LastBlockHeight: height,
   317  		LastBlockID:     types.BlockID{Hash: []byte(blockHash)},
   318  		LastBlockTime:   time.Now(),
   319  		LastResultsHash: []byte("last_results_hash"),
   320  		AppHash:         []byte(appHash),
   321  
   322  		LastValidators: &types.ValidatorSet{},
   323  		Validators:     &types.ValidatorSet{},
   324  		NextValidators: &types.ValidatorSet{},
   325  
   326  		ConsensusParams:                  *types.DefaultConsensusParams(),
   327  		LastHeightConsensusParamsChanged: 1,
   328  	}
   329  	state.ConsensusParams.Version.AppVersion = testAppVersion
   330  
   331  	commit := &types.Commit{BlockID: types.BlockID{Hash: []byte(blockHash)}}
   332  
   333  	return state, commit
   334  }
   335  
   336  func makeMockStateProvider(appHash string, height uint64) *mocks.StateProvider {
   337  	state, commit := makeTestStateAndCommit(appHash, int64(height))
   338  
   339  	stateProvider := &mocks.StateProvider{}
   340  	stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte(appHash), nil)
   341  	stateProvider.On("State", mock.Anything, height).Return(state, nil)
   342  	stateProvider.On("Commit", mock.Anything, height).Return(commit, nil)
   343  	return stateProvider
   344  }
   345  
   346  func TestSync(t *testing.T) {
   347  	// prepare
   348  	height, format := uint64(1), uint32(1)
   349  	appHash := "app_hash"
   350  
   351  	chunks := []*chunk{
   352  		{Height: height, Format: format, Index: 0, Chunk: []byte{1, 1, 0}},
   353  		{Height: height, Format: format, Index: 1, Chunk: []byte{1, 1, 1}},
   354  		{Height: height, Format: format, Index: 2, Chunk: []byte{1, 1, 2}},
   355  	}
   356  	snapshot := &snapshot{Height: height, Format: format, Chunks: uint32(len(chunks)), Hash: []byte{1, 2, 3}}
   357  	reactor := makeTestStateSyncReactor(t, appHash, int64(height), snapshot, chunks)
   358  	stateProvider := makeMockStateProvider(appHash, height)
   359  
   360  	// test
   361  	state, previousState, commit, err := reactor.Sync(stateProvider, 5*time.Second)
   362  
   363  	// verify
   364  	require.NoError(t, err)
   365  	require.NotNil(t, state)
   366  	require.NotNil(t, previousState)
   367  	require.NotNil(t, commit)
   368  }