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 }