github.com/Finschia/ostracon@v1.1.5/statesync/syncer_test.go (about)

     1  package statesync
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/mock"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	abci "github.com/tendermint/tendermint/abci/types"
    14  	tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
    15  	ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
    16  	tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
    17  
    18  	"github.com/Finschia/ostracon/config"
    19  	"github.com/Finschia/ostracon/libs/log"
    20  	tmsync "github.com/Finschia/ostracon/libs/sync"
    21  	"github.com/Finschia/ostracon/light"
    22  	"github.com/Finschia/ostracon/p2p"
    23  	p2pmocks "github.com/Finschia/ostracon/p2p/mocks"
    24  	"github.com/Finschia/ostracon/proxy"
    25  	proxymocks "github.com/Finschia/ostracon/proxy/mocks"
    26  	sm "github.com/Finschia/ostracon/state"
    27  	"github.com/Finschia/ostracon/statesync/mocks"
    28  	"github.com/Finschia/ostracon/types"
    29  	"github.com/Finschia/ostracon/version"
    30  )
    31  
    32  const testAppVersion = 9
    33  
    34  // Sets up a basic syncer that can be used to test OfferSnapshot requests
    35  func setupOfferSyncer(t *testing.T) (*syncer, *proxymocks.AppConnSnapshot) {
    36  	connQuery := &proxymocks.AppConnQuery{}
    37  	connSnapshot := &proxymocks.AppConnSnapshot{}
    38  	stateProvider := &mocks.StateProvider{}
    39  	stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
    40  	cfg := config.DefaultStateSyncConfig()
    41  	syncer := newSyncer(*cfg, log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
    42  
    43  	return syncer, connSnapshot
    44  }
    45  
    46  // Sets up a simple peer mock with an ID
    47  func simplePeer(id string) *p2pmocks.Peer {
    48  	peer := &p2pmocks.Peer{}
    49  	peer.On("ID").Return(p2p.ID(id))
    50  	return peer
    51  }
    52  
    53  func TestSyncer_SyncAny(t *testing.T) {
    54  	state := sm.State{
    55  		ChainID: "chain",
    56  		Version: tmstate.Version{
    57  			Consensus: tmversion.Consensus{
    58  				Block: version.BlockProtocol,
    59  				App:   testAppVersion,
    60  			},
    61  
    62  			Software: version.OCCoreSemVer,
    63  		},
    64  
    65  		LastBlockHeight: 1,
    66  		LastBlockID:     types.BlockID{Hash: []byte("blockhash")},
    67  		LastBlockTime:   time.Now(),
    68  		LastResultsHash: []byte("last_results_hash"),
    69  		AppHash:         []byte("app_hash"),
    70  
    71  		LastValidators: &types.ValidatorSet{},
    72  		Validators:     &types.ValidatorSet{},
    73  		NextValidators: &types.ValidatorSet{},
    74  
    75  		ConsensusParams:                  *types.DefaultConsensusParams(),
    76  		LastHeightConsensusParamsChanged: 1,
    77  	}
    78  	commit := &types.Commit{BlockID: types.BlockID{Hash: []byte("blockhash")}}
    79  
    80  	chunks := []*chunk{
    81  		{Height: 1, Format: 1, Index: 0, Chunk: []byte{1, 1, 0}},
    82  		{Height: 1, Format: 1, Index: 1, Chunk: []byte{1, 1, 1}},
    83  		{Height: 1, Format: 1, Index: 2, Chunk: []byte{1, 1, 2}},
    84  	}
    85  	s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
    86  
    87  	stateProvider := &mocks.StateProvider{}
    88  	stateProvider.On("AppHash", mock.Anything, uint64(1)).Return(state.AppHash, nil)
    89  	stateProvider.On("AppHash", mock.Anything, uint64(2)).Return([]byte("app_hash_2"), nil)
    90  	stateProvider.On("Commit", mock.Anything, uint64(1)).Return(commit, nil)
    91  	stateProvider.On("State", mock.Anything, uint64(1)).Return(state, nil)
    92  	connSnapshot := &proxymocks.AppConnSnapshot{}
    93  	connQuery := &proxymocks.AppConnQuery{}
    94  
    95  	cfg := config.DefaultStateSyncConfig()
    96  	syncer := newSyncer(*cfg, log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
    97  
    98  	// Adding a chunk should error when no sync is in progress
    99  	_, err := syncer.AddChunk(&chunk{Height: 1, Format: 1, Index: 0, Chunk: []byte{1}})
   100  	require.Error(t, err)
   101  
   102  	// Adding a couple of peers should trigger snapshot discovery messages
   103  	peerA := &Peer{Peer: &p2pmocks.Peer{}, EnvelopeSender: &p2pmocks.EnvelopeSender{}}
   104  	peerA.Peer.On("ID").Return(p2p.ID("a"))
   105  	peerA.EnvelopeSender.On("SendEnvelope", mock.MatchedBy(func(i interface{}) bool {
   106  		e, ok := i.(p2p.Envelope)
   107  		if !ok {
   108  			return false
   109  		}
   110  		req, ok := e.Message.(*ssproto.SnapshotsRequest)
   111  		return ok && e.ChannelID == SnapshotChannel && req != nil
   112  	})).Return(true)
   113  	syncer.AddPeer(peerA)
   114  	peerA.Peer.AssertExpectations(t)
   115  	peerA.EnvelopeSender.AssertExpectations(t)
   116  
   117  	peerB := &Peer{Peer: &p2pmocks.Peer{}, EnvelopeSender: &p2pmocks.EnvelopeSender{}}
   118  	peerB.Peer.On("ID").Return(p2p.ID("b"))
   119  	peerB.EnvelopeSender.On("SendEnvelope", mock.MatchedBy(func(i interface{}) bool {
   120  		e, ok := i.(p2p.Envelope)
   121  		if !ok {
   122  			return false
   123  		}
   124  		req, ok := e.Message.(*ssproto.SnapshotsRequest)
   125  		return ok && e.ChannelID == SnapshotChannel && req != nil
   126  	})).Return(true)
   127  	syncer.AddPeer(peerB)
   128  	peerB.Peer.AssertExpectations(t)
   129  	peerB.EnvelopeSender.AssertExpectations(t)
   130  
   131  	// Both peers report back with snapshots. One of them also returns a snapshot we don't want, in
   132  	// format 2, which will be rejected by the ABCI application.
   133  	new, err := syncer.AddSnapshot(peerA, s)
   134  	require.NoError(t, err)
   135  	assert.True(t, new)
   136  
   137  	new, err = syncer.AddSnapshot(peerB, s)
   138  	require.NoError(t, err)
   139  	assert.False(t, new)
   140  
   141  	new, err = syncer.AddSnapshot(peerB, &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1}})
   142  	require.NoError(t, err)
   143  	assert.True(t, new)
   144  
   145  	// We start a sync, with peers sending back chunks when requested. We first reject the snapshot
   146  	// with height 2 format 2, and accept the snapshot at height 1.
   147  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   148  		Snapshot: &abci.Snapshot{
   149  			Height: 2,
   150  			Format: 2,
   151  			Chunks: 3,
   152  			Hash:   []byte{1},
   153  		},
   154  		AppHash: []byte("app_hash_2"),
   155  	}).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil)
   156  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   157  		Snapshot: &abci.Snapshot{
   158  			Height:   s.Height,
   159  			Format:   s.Format,
   160  			Chunks:   s.Chunks,
   161  			Hash:     s.Hash,
   162  			Metadata: s.Metadata,
   163  		},
   164  		AppHash: []byte("app_hash"),
   165  	}).Times(2).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil)
   166  
   167  	chunkRequests := make(map[uint32]int)
   168  	chunkRequestsMtx := tmsync.Mutex{}
   169  	onChunkRequest := func(args mock.Arguments) {
   170  		e, ok := args[0].(p2p.Envelope)
   171  		require.True(t, ok)
   172  		msg := e.Message.(*ssproto.ChunkRequest)
   173  		require.EqualValues(t, 1, msg.Height)
   174  		require.EqualValues(t, 1, msg.Format)
   175  		require.LessOrEqual(t, msg.Index, uint32(len(chunks)))
   176  
   177  		added, err := syncer.AddChunk(chunks[msg.Index])
   178  		require.NoError(t, err)
   179  		assert.True(t, added)
   180  
   181  		chunkRequestsMtx.Lock()
   182  		chunkRequests[msg.Index]++
   183  		chunkRequestsMtx.Unlock()
   184  	}
   185  	peerA.EnvelopeSender.On("SendEnvelope", mock.MatchedBy(func(i interface{}) bool {
   186  		e, ok := i.(p2p.Envelope)
   187  		return ok && e.ChannelID == ChunkChannel
   188  	})).Maybe().Run(onChunkRequest).Return(true)
   189  	peerB.EnvelopeSender.On("SendEnvelope", mock.MatchedBy(func(i interface{}) bool {
   190  		e, ok := i.(p2p.Envelope)
   191  		return ok && e.ChannelID == ChunkChannel
   192  	})).Maybe().Run(onChunkRequest).Return(true)
   193  
   194  	// The first time we're applying chunk 2 we tell it to retry the snapshot and discard chunk 1,
   195  	// which should cause it to keep the existing chunk 0 and 2, and restart restoration from
   196  	// beginning. We also wait for a little while, to exercise the retry logic in fetchChunks().
   197  	connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   198  		Index: 2, Chunk: []byte{1, 1, 2},
   199  	}).Once().Run(func(args mock.Arguments) { time.Sleep(2 * time.Second) }).Return(
   200  		&abci.ResponseApplySnapshotChunk{
   201  			Result:        abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT,
   202  			RefetchChunks: []uint32{1},
   203  		}, nil)
   204  
   205  	connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   206  		Index: 0, Chunk: []byte{1, 1, 0},
   207  	}).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   208  	connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   209  		Index: 1, Chunk: []byte{1, 1, 1},
   210  	}).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   211  	connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   212  		Index: 2, Chunk: []byte{1, 1, 2},
   213  	}).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   214  	connQuery.On("InfoSync", proxy.RequestInfo).Return(&abci.ResponseInfo{
   215  		AppVersion:       testAppVersion,
   216  		LastBlockHeight:  1,
   217  		LastBlockAppHash: []byte("app_hash"),
   218  	}, nil)
   219  
   220  	newState, previousState, lastCommit, err := syncer.SyncAny(0, func() {})
   221  	require.NoError(t, err)
   222  
   223  	time.Sleep(50 * time.Millisecond) // wait for peers to receive requests
   224  
   225  	chunkRequestsMtx.Lock()
   226  	assert.Equal(t, map[uint32]int{0: 1, 1: 2, 2: 1}, chunkRequests)
   227  	chunkRequestsMtx.Unlock()
   228  
   229  	expectState := state
   230  	expectPreviousState := sm.State{}
   231  
   232  	assert.Equal(t, expectState, newState)
   233  	assert.Equal(t, expectPreviousState, previousState)
   234  	assert.Equal(t, commit, lastCommit)
   235  
   236  	connSnapshot.AssertExpectations(t)
   237  	connQuery.AssertExpectations(t)
   238  	peerA.Peer.AssertExpectations(t)
   239  	peerA.EnvelopeSender.AssertExpectations(t)
   240  	peerB.Peer.AssertExpectations(t)
   241  	peerB.EnvelopeSender.AssertExpectations(t)
   242  }
   243  
   244  func TestSyncer_SyncAny_noSnapshots(t *testing.T) {
   245  	syncer, _ := setupOfferSyncer(t)
   246  	_, _, _, err := syncer.SyncAny(0, func() {})
   247  	assert.Equal(t, errNoSnapshots, err)
   248  }
   249  
   250  func TestSyncer_SyncAny_abort(t *testing.T) {
   251  	syncer, connSnapshot := setupOfferSyncer(t)
   252  
   253  	s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
   254  	_, err := syncer.AddSnapshot(simplePeer("id"), s)
   255  	require.NoError(t, err)
   256  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   257  		Snapshot: toABCI(s), AppHash: []byte("app_hash"),
   258  	}).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil)
   259  
   260  	_, _, _, err = syncer.SyncAny(0, func() {})
   261  	assert.Equal(t, errAbort, err)
   262  	connSnapshot.AssertExpectations(t)
   263  }
   264  
   265  func TestSyncer_SyncAny_reject(t *testing.T) {
   266  	syncer, connSnapshot := setupOfferSyncer(t)
   267  
   268  	// s22 is tried first, then s12, then s11, then errNoSnapshots
   269  	s22 := &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
   270  	s12 := &snapshot{Height: 1, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
   271  	s11 := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
   272  	_, err := syncer.AddSnapshot(simplePeer("id"), s22)
   273  	require.NoError(t, err)
   274  	_, err = syncer.AddSnapshot(simplePeer("id"), s12)
   275  	require.NoError(t, err)
   276  	_, err = syncer.AddSnapshot(simplePeer("id"), s11)
   277  	require.NoError(t, err)
   278  
   279  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   280  		Snapshot: toABCI(s22), AppHash: []byte("app_hash"),
   281  	}).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
   282  
   283  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   284  		Snapshot: toABCI(s12), AppHash: []byte("app_hash"),
   285  	}).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
   286  
   287  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   288  		Snapshot: toABCI(s11), AppHash: []byte("app_hash"),
   289  	}).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
   290  
   291  	_, _, _, err = syncer.SyncAny(0, func() {})
   292  	assert.Equal(t, errNoSnapshots, err)
   293  	connSnapshot.AssertExpectations(t)
   294  }
   295  
   296  func TestSyncer_SyncAny_reject_format(t *testing.T) {
   297  	syncer, connSnapshot := setupOfferSyncer(t)
   298  
   299  	// s22 is tried first, which reject s22 and s12, then s11 will abort.
   300  	s22 := &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
   301  	s12 := &snapshot{Height: 1, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
   302  	s11 := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
   303  	_, err := syncer.AddSnapshot(simplePeer("id"), s22)
   304  	require.NoError(t, err)
   305  	_, err = syncer.AddSnapshot(simplePeer("id"), s12)
   306  	require.NoError(t, err)
   307  	_, err = syncer.AddSnapshot(simplePeer("id"), s11)
   308  	require.NoError(t, err)
   309  
   310  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   311  		Snapshot: toABCI(s22), AppHash: []byte("app_hash"),
   312  	}).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil)
   313  
   314  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   315  		Snapshot: toABCI(s11), AppHash: []byte("app_hash"),
   316  	}).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil)
   317  
   318  	_, _, _, err = syncer.SyncAny(0, func() {})
   319  	assert.Equal(t, errAbort, err)
   320  	connSnapshot.AssertExpectations(t)
   321  }
   322  
   323  func TestSyncer_SyncAny_reject_sender(t *testing.T) {
   324  	syncer, connSnapshot := setupOfferSyncer(t)
   325  
   326  	peerA := simplePeer("a")
   327  	peerB := simplePeer("b")
   328  	peerC := simplePeer("c")
   329  
   330  	// sbc will be offered first, which will be rejected with reject_sender, causing all snapshots
   331  	// submitted by both b and c (i.e. sb, sc, sbc) to be rejected. Finally, sa will reject and
   332  	// errNoSnapshots is returned.
   333  	sa := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
   334  	sb := &snapshot{Height: 2, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
   335  	sc := &snapshot{Height: 3, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
   336  	sbc := &snapshot{Height: 4, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
   337  	_, err := syncer.AddSnapshot(peerA, sa)
   338  	require.NoError(t, err)
   339  	_, err = syncer.AddSnapshot(peerB, sb)
   340  	require.NoError(t, err)
   341  	_, err = syncer.AddSnapshot(peerC, sc)
   342  	require.NoError(t, err)
   343  	_, err = syncer.AddSnapshot(peerB, sbc)
   344  	require.NoError(t, err)
   345  	_, err = syncer.AddSnapshot(peerC, sbc)
   346  	require.NoError(t, err)
   347  
   348  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   349  		Snapshot: toABCI(sbc), AppHash: []byte("app_hash"),
   350  	}).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_SENDER}, nil)
   351  
   352  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   353  		Snapshot: toABCI(sa), AppHash: []byte("app_hash"),
   354  	}).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
   355  
   356  	_, _, _, err = syncer.SyncAny(0, func() {})
   357  	assert.Equal(t, errNoSnapshots, err)
   358  	connSnapshot.AssertExpectations(t)
   359  }
   360  
   361  func TestSyncer_SyncAny_abciError(t *testing.T) {
   362  	syncer, connSnapshot := setupOfferSyncer(t)
   363  
   364  	errBoom := errors.New("boom")
   365  	s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
   366  	_, err := syncer.AddSnapshot(simplePeer("id"), s)
   367  	require.NoError(t, err)
   368  	connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   369  		Snapshot: toABCI(s), AppHash: []byte("app_hash"),
   370  	}).Once().Return(nil, errBoom)
   371  
   372  	_, _, _, err = syncer.SyncAny(0, func() {})
   373  	assert.True(t, errors.Is(err, errBoom))
   374  	connSnapshot.AssertExpectations(t)
   375  }
   376  
   377  func TestSyncer_offerSnapshot(t *testing.T) {
   378  	unknownErr := errors.New("unknown error")
   379  	boom := errors.New("boom")
   380  
   381  	testcases := map[string]struct {
   382  		result    abci.ResponseOfferSnapshot_Result
   383  		err       error
   384  		expectErr error
   385  	}{
   386  		"accept":           {abci.ResponseOfferSnapshot_ACCEPT, nil, nil},
   387  		"abort":            {abci.ResponseOfferSnapshot_ABORT, nil, errAbort},
   388  		"reject":           {abci.ResponseOfferSnapshot_REJECT, nil, errRejectSnapshot},
   389  		"reject_format":    {abci.ResponseOfferSnapshot_REJECT_FORMAT, nil, errRejectFormat},
   390  		"reject_sender":    {abci.ResponseOfferSnapshot_REJECT_SENDER, nil, errRejectSender},
   391  		"unknown":          {abci.ResponseOfferSnapshot_UNKNOWN, nil, unknownErr},
   392  		"error":            {0, boom, boom},
   393  		"unknown non-zero": {9, nil, unknownErr},
   394  	}
   395  	for name, tc := range testcases {
   396  		tc := tc
   397  		t.Run(name, func(t *testing.T) {
   398  			syncer, connSnapshot := setupOfferSyncer(t)
   399  			s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")}
   400  			connSnapshot.On("OfferSnapshotSync", abci.RequestOfferSnapshot{
   401  				Snapshot: toABCI(s),
   402  				AppHash:  []byte("app_hash"),
   403  			}).Return(&abci.ResponseOfferSnapshot{Result: tc.result}, tc.err)
   404  			err := syncer.offerSnapshot(s)
   405  			if tc.expectErr == unknownErr {
   406  				require.Error(t, err)
   407  			} else {
   408  				unwrapped := errors.Unwrap(err)
   409  				if unwrapped != nil {
   410  					err = unwrapped
   411  				}
   412  				assert.Equal(t, tc.expectErr, err)
   413  			}
   414  		})
   415  	}
   416  }
   417  
   418  func TestSyncer_applyChunks_Results(t *testing.T) {
   419  	unknownErr := errors.New("unknown error")
   420  	boom := errors.New("boom")
   421  
   422  	testcases := map[string]struct {
   423  		result    abci.ResponseApplySnapshotChunk_Result
   424  		err       error
   425  		expectErr error
   426  	}{
   427  		"accept":           {abci.ResponseApplySnapshotChunk_ACCEPT, nil, nil},
   428  		"abort":            {abci.ResponseApplySnapshotChunk_ABORT, nil, errAbort},
   429  		"retry":            {abci.ResponseApplySnapshotChunk_RETRY, nil, nil},
   430  		"retry_snapshot":   {abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT, nil, errRetrySnapshot},
   431  		"reject_snapshot":  {abci.ResponseApplySnapshotChunk_REJECT_SNAPSHOT, nil, errRejectSnapshot},
   432  		"unknown":          {abci.ResponseApplySnapshotChunk_UNKNOWN, nil, unknownErr},
   433  		"error":            {0, boom, boom},
   434  		"unknown non-zero": {9, nil, unknownErr},
   435  	}
   436  	for name, tc := range testcases {
   437  		tc := tc
   438  		t.Run(name, func(t *testing.T) {
   439  			connQuery := &proxymocks.AppConnQuery{}
   440  			connSnapshot := &proxymocks.AppConnSnapshot{}
   441  			stateProvider := &mocks.StateProvider{}
   442  			stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
   443  
   444  			cfg := config.DefaultStateSyncConfig()
   445  			syncer := newSyncer(*cfg, log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
   446  
   447  			body := []byte{1, 2, 3}
   448  			chunks, err := newChunkQueue(&snapshot{Height: 1, Format: 1, Chunks: 1}, "")
   449  			require.NoError(t, err)
   450  			_, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: body})
   451  			require.NoError(t, err)
   452  
   453  			connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   454  				Index: 0, Chunk: body,
   455  			}).Once().Return(&abci.ResponseApplySnapshotChunk{Result: tc.result}, tc.err)
   456  			if tc.result == abci.ResponseApplySnapshotChunk_RETRY {
   457  				connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   458  					Index: 0, Chunk: body,
   459  				}).Once().Return(&abci.ResponseApplySnapshotChunk{
   460  					Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   461  			}
   462  
   463  			err = syncer.applyChunks(chunks)
   464  			if tc.expectErr == unknownErr {
   465  				require.Error(t, err)
   466  			} else {
   467  				unwrapped := errors.Unwrap(err)
   468  				if unwrapped != nil {
   469  					err = unwrapped
   470  				}
   471  				assert.Equal(t, tc.expectErr, err)
   472  			}
   473  			connSnapshot.AssertExpectations(t)
   474  		})
   475  	}
   476  }
   477  
   478  func TestSyncer_applyChunks_RefetchChunks(t *testing.T) {
   479  	// Discarding chunks via refetch_chunks should work the same for all results
   480  	testcases := map[string]struct {
   481  		result abci.ResponseApplySnapshotChunk_Result
   482  	}{
   483  		"accept":          {abci.ResponseApplySnapshotChunk_ACCEPT},
   484  		"abort":           {abci.ResponseApplySnapshotChunk_ABORT},
   485  		"retry":           {abci.ResponseApplySnapshotChunk_RETRY},
   486  		"retry_snapshot":  {abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT},
   487  		"reject_snapshot": {abci.ResponseApplySnapshotChunk_REJECT_SNAPSHOT},
   488  	}
   489  	for name, tc := range testcases {
   490  		tc := tc
   491  		t.Run(name, func(t *testing.T) {
   492  			connQuery := &proxymocks.AppConnQuery{}
   493  			connSnapshot := &proxymocks.AppConnSnapshot{}
   494  			stateProvider := &mocks.StateProvider{}
   495  			stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
   496  
   497  			cfg := config.DefaultStateSyncConfig()
   498  			syncer := newSyncer(*cfg, log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
   499  
   500  			chunks, err := newChunkQueue(&snapshot{Height: 1, Format: 1, Chunks: 3}, "")
   501  			require.NoError(t, err)
   502  			added, err := chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: []byte{0}})
   503  			require.True(t, added)
   504  			require.NoError(t, err)
   505  			added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 1, Chunk: []byte{1}})
   506  			require.True(t, added)
   507  			require.NoError(t, err)
   508  			added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 2, Chunk: []byte{2}})
   509  			require.True(t, added)
   510  			require.NoError(t, err)
   511  
   512  			// The first two chunks are accepted, before the last one asks for 1 to be refetched
   513  			connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   514  				Index: 0, Chunk: []byte{0},
   515  			}).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   516  			connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   517  				Index: 1, Chunk: []byte{1},
   518  			}).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   519  			connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   520  				Index: 2, Chunk: []byte{2},
   521  			}).Once().Return(&abci.ResponseApplySnapshotChunk{
   522  				Result:        tc.result,
   523  				RefetchChunks: []uint32{1},
   524  			}, nil)
   525  
   526  			// Since removing the chunk will cause Next() to block, we spawn a goroutine, then
   527  			// check the queue contents, and finally close the queue to end the goroutine.
   528  			// We don't really care about the result of applyChunks, since it has separate test.
   529  			go func() {
   530  				syncer.applyChunks(chunks) //nolint:errcheck // purposefully ignore error
   531  			}()
   532  
   533  			time.Sleep(50 * time.Millisecond)
   534  			assert.True(t, chunks.Has(0))
   535  			assert.False(t, chunks.Has(1))
   536  			assert.True(t, chunks.Has(2))
   537  			err = chunks.Close()
   538  			require.NoError(t, err)
   539  		})
   540  	}
   541  }
   542  
   543  func TestSyncer_applyChunks_RejectSenders(t *testing.T) {
   544  	// Banning chunks senders via ban_chunk_senders should work the same for all results
   545  	testcases := map[string]struct {
   546  		result abci.ResponseApplySnapshotChunk_Result
   547  	}{
   548  		"accept":          {abci.ResponseApplySnapshotChunk_ACCEPT},
   549  		"abort":           {abci.ResponseApplySnapshotChunk_ABORT},
   550  		"retry":           {abci.ResponseApplySnapshotChunk_RETRY},
   551  		"retry_snapshot":  {abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT},
   552  		"reject_snapshot": {abci.ResponseApplySnapshotChunk_REJECT_SNAPSHOT},
   553  	}
   554  	for name, tc := range testcases {
   555  		tc := tc
   556  		t.Run(name, func(t *testing.T) {
   557  			connQuery := &proxymocks.AppConnQuery{}
   558  			connSnapshot := &proxymocks.AppConnSnapshot{}
   559  			stateProvider := &mocks.StateProvider{}
   560  			stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
   561  
   562  			cfg := config.DefaultStateSyncConfig()
   563  			syncer := newSyncer(*cfg, log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
   564  
   565  			// Set up three peers across two snapshots, and ask for one of them to be banned.
   566  			// It should be banned from all snapshots.
   567  			peerA := simplePeer("a")
   568  			peerB := simplePeer("b")
   569  			peerC := simplePeer("c")
   570  
   571  			s1 := &snapshot{Height: 1, Format: 1, Chunks: 3}
   572  			s2 := &snapshot{Height: 2, Format: 1, Chunks: 3}
   573  			_, err := syncer.AddSnapshot(peerA, s1)
   574  			require.NoError(t, err)
   575  			_, err = syncer.AddSnapshot(peerA, s2)
   576  			require.NoError(t, err)
   577  			_, err = syncer.AddSnapshot(peerB, s1)
   578  			require.NoError(t, err)
   579  			_, err = syncer.AddSnapshot(peerB, s2)
   580  			require.NoError(t, err)
   581  			_, err = syncer.AddSnapshot(peerC, s1)
   582  			require.NoError(t, err)
   583  			_, err = syncer.AddSnapshot(peerC, s2)
   584  			require.NoError(t, err)
   585  
   586  			chunks, err := newChunkQueue(s1, "")
   587  			require.NoError(t, err)
   588  			added, err := chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: []byte{0}, Sender: peerA.ID()})
   589  			require.True(t, added)
   590  			require.NoError(t, err)
   591  			added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 1, Chunk: []byte{1}, Sender: peerB.ID()})
   592  			require.True(t, added)
   593  			require.NoError(t, err)
   594  			added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 2, Chunk: []byte{2}, Sender: peerC.ID()})
   595  			require.True(t, added)
   596  			require.NoError(t, err)
   597  
   598  			// The first two chunks are accepted, before the last one asks for b sender to be rejected
   599  			connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   600  				Index: 0, Chunk: []byte{0}, Sender: "a",
   601  			}).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   602  			connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   603  				Index: 1, Chunk: []byte{1}, Sender: "b",
   604  			}).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   605  			connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   606  				Index: 2, Chunk: []byte{2}, Sender: "c",
   607  			}).Once().Return(&abci.ResponseApplySnapshotChunk{
   608  				Result:        tc.result,
   609  				RejectSenders: []string{string(peerB.ID())},
   610  			}, nil)
   611  
   612  			// On retry, the last chunk will be tried again, so we just accept it then.
   613  			if tc.result == abci.ResponseApplySnapshotChunk_RETRY {
   614  				connSnapshot.On("ApplySnapshotChunkSync", abci.RequestApplySnapshotChunk{
   615  					Index: 2, Chunk: []byte{2}, Sender: "c",
   616  				}).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
   617  			}
   618  
   619  			// We don't really care about the result of applyChunks, since it has separate test.
   620  			// However, it will block on e.g. retry result, so we spawn a goroutine that will
   621  			// be shut down when the chunk queue closes.
   622  			go func() {
   623  				syncer.applyChunks(chunks) //nolint:errcheck // purposefully ignore error
   624  			}()
   625  
   626  			time.Sleep(50 * time.Millisecond)
   627  
   628  			s1peers := syncer.snapshots.GetPeers(s1)
   629  			assert.Len(t, s1peers, 2)
   630  			assert.EqualValues(t, "a", s1peers[0].ID())
   631  			assert.EqualValues(t, "c", s1peers[1].ID())
   632  
   633  			syncer.snapshots.GetPeers(s1)
   634  			assert.Len(t, s1peers, 2)
   635  			assert.EqualValues(t, "a", s1peers[0].ID())
   636  			assert.EqualValues(t, "c", s1peers[1].ID())
   637  
   638  			err = chunks.Close()
   639  			require.NoError(t, err)
   640  		})
   641  	}
   642  }
   643  
   644  func TestSyncer_verifyApp(t *testing.T) {
   645  	boom := errors.New("boom")
   646  	const appVersion = 9
   647  	const invalidAppVersion = appVersion + 1
   648  	appVersionMismatchErr := fmt.Errorf("app version mismatch. Expected: %d, got: %d", appVersion, invalidAppVersion)
   649  	s := &snapshot{Height: 3, Format: 1, Chunks: 5, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")}
   650  
   651  	testcases := map[string]struct {
   652  		response  *abci.ResponseInfo
   653  		err       error
   654  		expectErr error
   655  	}{
   656  		"verified": {&abci.ResponseInfo{
   657  			LastBlockHeight:  3,
   658  			LastBlockAppHash: []byte("app_hash"),
   659  			AppVersion:       appVersion,
   660  		}, nil, nil},
   661  		"invalid app version": {&abci.ResponseInfo{
   662  			LastBlockHeight:  3,
   663  			LastBlockAppHash: []byte("app_hash"),
   664  			AppVersion:       invalidAppVersion,
   665  		}, nil, appVersionMismatchErr},
   666  		"invalid height": {&abci.ResponseInfo{
   667  			LastBlockHeight:  5,
   668  			LastBlockAppHash: []byte("app_hash"),
   669  			AppVersion:       appVersion,
   670  		}, nil, errVerifyFailed},
   671  		"invalid hash": {&abci.ResponseInfo{
   672  			LastBlockHeight:  3,
   673  			LastBlockAppHash: []byte("xxx"),
   674  			AppVersion:       appVersion,
   675  		}, nil, errVerifyFailed},
   676  		"error": {nil, boom, boom},
   677  	}
   678  	for name, tc := range testcases {
   679  		tc := tc
   680  		t.Run(name, func(t *testing.T) {
   681  			connQuery := &proxymocks.AppConnQuery{}
   682  			connSnapshot := &proxymocks.AppConnSnapshot{}
   683  			stateProvider := &mocks.StateProvider{}
   684  
   685  			cfg := config.DefaultStateSyncConfig()
   686  			syncer := newSyncer(*cfg, log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
   687  
   688  			connQuery.On("InfoSync", proxy.RequestInfo).Return(tc.response, tc.err)
   689  			err := syncer.verifyApp(s, appVersion)
   690  			unwrapped := errors.Unwrap(err)
   691  			if unwrapped != nil {
   692  				err = unwrapped
   693  			}
   694  			require.Equal(t, tc.expectErr, err)
   695  		})
   696  	}
   697  }
   698  
   699  func TestSyncer_Sync(t *testing.T) {
   700  	connQuery := &proxymocks.AppConnQuery{}
   701  	connSnapshot := &proxymocks.AppConnSnapshot{}
   702  	stateProvider := &mocks.StateProvider{}
   703  
   704  	cfg := config.DefaultStateSyncConfig()
   705  	syncer := newSyncer(*cfg, log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
   706  	snapshot := &snapshot{}
   707  	chunkQueue := &chunkQueue{}
   708  
   709  	{
   710  		stateProvider.On("AppHash", mock.Anything, mock.Anything).Once().Return(nil, light.ErrNoWitnesses)
   711  
   712  		state, previousState, commit, err := syncer.Sync(snapshot, chunkQueue)
   713  		require.Error(t, err)
   714  		require.Equal(t, light.ErrNoWitnesses, err)
   715  		require.NotNil(t, state)
   716  		require.NotNil(t, previousState)
   717  		require.Nil(t, commit)
   718  	}
   719  	{
   720  		stateProvider.On("AppHash", mock.Anything, mock.Anything).Once().Return(nil, errRejectSnapshot)
   721  
   722  		state, previousState, commit, err := syncer.Sync(snapshot, chunkQueue)
   723  		require.Error(t, err)
   724  		require.Equal(t, errRejectSnapshot, err)
   725  		require.NotNil(t, state)
   726  		require.NotNil(t, previousState)
   727  		require.Nil(t, commit)
   728  	}
   729  
   730  	stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
   731  	connSnapshot.On("OfferSnapshotSync", mock.Anything).Return(
   732  		&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil)
   733  	snapshot.Height = 2
   734  
   735  	{
   736  		stateProvider.On("State", mock.Anything, snapshot.Height).Once().Return(sm.State{}, light.ErrNoWitnesses)
   737  
   738  		state, previousState, commit, err := syncer.Sync(snapshot, chunkQueue)
   739  		require.Error(t, err)
   740  		require.Equal(t, light.ErrNoWitnesses, err)
   741  		require.NotNil(t, state)
   742  		require.NotNil(t, previousState)
   743  		require.Nil(t, commit)
   744  	}
   745  
   746  	{
   747  		stateProvider.On("State", mock.Anything, snapshot.Height).Once().Return(sm.State{}, errRejectSnapshot)
   748  
   749  		state, previousState, commit, err := syncer.Sync(snapshot, chunkQueue)
   750  		require.Error(t, err)
   751  		require.Equal(t, errRejectSnapshot, err)
   752  		require.NotNil(t, state)
   753  		require.NotNil(t, previousState)
   754  		require.Nil(t, commit)
   755  	}
   756  
   757  	stateProvider.On("State", mock.Anything, snapshot.Height).Return(sm.State{}, nil)
   758  
   759  	{
   760  		stateProvider.On("State", mock.Anything, snapshot.Height-1).Once().Return(sm.State{}, light.ErrNoWitnesses)
   761  
   762  		state, previousState, commit, err := syncer.Sync(snapshot, chunkQueue)
   763  		require.Error(t, err)
   764  		require.Equal(t, light.ErrNoWitnesses, err)
   765  		require.NotNil(t, state)
   766  		require.NotNil(t, previousState)
   767  		require.Nil(t, commit)
   768  	}
   769  
   770  	{
   771  		stateProvider.On("State", mock.Anything, snapshot.Height-1).Once().Return(sm.State{}, errRejectSnapshot)
   772  
   773  		state, previousState, commit, err := syncer.Sync(snapshot, chunkQueue)
   774  		require.Error(t, err)
   775  		require.Equal(t, errRejectSnapshot, err)
   776  		require.NotNil(t, state)
   777  		require.NotNil(t, previousState)
   778  		require.Nil(t, commit)
   779  	}
   780  
   781  	stateProvider.On("State", mock.Anything, snapshot.Height-1).Return(sm.State{}, nil)
   782  
   783  	{
   784  		stateProvider.On("Commit", mock.Anything, snapshot.Height).Once().Return(nil, light.ErrNoWitnesses)
   785  
   786  		state, previousState, commit, err := syncer.Sync(snapshot, chunkQueue)
   787  		require.Error(t, err)
   788  		require.Equal(t, light.ErrNoWitnesses, err)
   789  		require.NotNil(t, state)
   790  		require.NotNil(t, previousState)
   791  		require.Nil(t, commit)
   792  	}
   793  
   794  	{
   795  		stateProvider.On("Commit", mock.Anything, snapshot.Height).Once().Return(nil, errRejectSnapshot)
   796  
   797  		state, previousState, commit, err := syncer.Sync(snapshot, chunkQueue)
   798  		require.Error(t, err)
   799  		require.Equal(t, errRejectSnapshot, err)
   800  		require.NotNil(t, state)
   801  		require.NotNil(t, previousState)
   802  		require.Nil(t, commit)
   803  	}
   804  }
   805  
   806  func toABCI(s *snapshot) *abci.Snapshot {
   807  	return &abci.Snapshot{
   808  		Height:   s.Height,
   809  		Format:   s.Format,
   810  		Chunks:   s.Chunks,
   811  		Hash:     s.Hash,
   812  		Metadata: s.Metadata,
   813  	}
   814  }