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

785 lines
27 KiB

statesync: remove deadlock on init fail (#7029) When statesync is stopped during shutdown, it has the possibility of deadlocking. A dump of goroutines reveals that this is related to the peerUpdates channel not returning anything on its `Done()` channel when `OnStop` is called. As this is occuring, `processPeerUpdate` is attempting to acquire the reactor lock. It appears that this lock can never be acquired. I looked for the places where the lock may remain locked accidentally and cleaned them up in hopes to eradicate the issue. Dumps of the relevant goroutines may be found below. Note that the line numbers below are relative to the code in the `v0.35.0-rc1` tag. ``` goroutine 36 [chan receive]: github.com/tendermint/tendermint/internal/statesync.(*Reactor).OnStop(0xc00058f200) github.com/tendermint/tendermint/internal/statesync/reactor.go:243 +0x117 github.com/tendermint/tendermint/libs/service.(*BaseService).Stop(0xc00058f200, 0x0, 0x0) github.com/tendermint/tendermint/libs/service/service.go:171 +0x323 github.com/tendermint/tendermint/node.(*nodeImpl).OnStop(0xc0001ea240) github.com/tendermint/tendermint/node/node.go:769 +0x132 github.com/tendermint/tendermint/libs/service.(*BaseService).Stop(0xc0001ea240, 0x0, 0x0) github.com/tendermint/tendermint/libs/service/service.go:171 +0x323 github.com/tendermint/tendermint/cmd/tendermint/commands.NewRunNodeCmd.func1.1() github.com/tendermint/tendermint/cmd/tendermint/commands/run_node.go:143 +0x62 github.com/tendermint/tendermint/libs/os.TrapSignal.func1(0xc000629500, 0x7fdb52f96358, 0xc0002b5030, 0xc00000daa0) github.com/tendermint/tendermint/libs/os/os.go:26 +0x102 created by github.com/tendermint/tendermint/libs/os.TrapSignal github.com/tendermint/tendermint/libs/os/os.go:22 +0xe6 goroutine 188 [semacquire]: sync.runtime_SemacquireMutex(0xc00026b1cc, 0x0, 0x1) runtime/sema.go:71 +0x47 sync.(*Mutex).lockSlow(0xc00026b1c8) sync/mutex.go:138 +0x105 sync.(*Mutex).Lock(...) sync/mutex.go:81 sync.(*RWMutex).Lock(0xc00026b1c8) sync/rwmutex.go:111 +0x90 github.com/tendermint/tendermint/internal/statesync.(*Reactor).processPeerUpdate(0xc00026b080, 0xc000650008, 0x28, 0x124de90, 0x4) github.com/tendermint/tendermint/internal/statesync/reactor.go:849 +0x1a5 github.com/tendermint/tendermint/internal/statesync.(*Reactor).processPeerUpdates(0xc00026b080) github.com/tendermint/tendermint/internal/statesync/reactor.go:883 +0xab created by github.com/tendermint/tendermint/internal/statesync.(*Reactor.OnStart github.com/tendermint/tendermint/internal/statesync/reactor.go:219 +0xcd) ```
3 years ago
statesync: remove deadlock on init fail (#7029) When statesync is stopped during shutdown, it has the possibility of deadlocking. A dump of goroutines reveals that this is related to the peerUpdates channel not returning anything on its `Done()` channel when `OnStop` is called. As this is occuring, `processPeerUpdate` is attempting to acquire the reactor lock. It appears that this lock can never be acquired. I looked for the places where the lock may remain locked accidentally and cleaned them up in hopes to eradicate the issue. Dumps of the relevant goroutines may be found below. Note that the line numbers below are relative to the code in the `v0.35.0-rc1` tag. ``` goroutine 36 [chan receive]: github.com/tendermint/tendermint/internal/statesync.(*Reactor).OnStop(0xc00058f200) github.com/tendermint/tendermint/internal/statesync/reactor.go:243 +0x117 github.com/tendermint/tendermint/libs/service.(*BaseService).Stop(0xc00058f200, 0x0, 0x0) github.com/tendermint/tendermint/libs/service/service.go:171 +0x323 github.com/tendermint/tendermint/node.(*nodeImpl).OnStop(0xc0001ea240) github.com/tendermint/tendermint/node/node.go:769 +0x132 github.com/tendermint/tendermint/libs/service.(*BaseService).Stop(0xc0001ea240, 0x0, 0x0) github.com/tendermint/tendermint/libs/service/service.go:171 +0x323 github.com/tendermint/tendermint/cmd/tendermint/commands.NewRunNodeCmd.func1.1() github.com/tendermint/tendermint/cmd/tendermint/commands/run_node.go:143 +0x62 github.com/tendermint/tendermint/libs/os.TrapSignal.func1(0xc000629500, 0x7fdb52f96358, 0xc0002b5030, 0xc00000daa0) github.com/tendermint/tendermint/libs/os/os.go:26 +0x102 created by github.com/tendermint/tendermint/libs/os.TrapSignal github.com/tendermint/tendermint/libs/os/os.go:22 +0xe6 goroutine 188 [semacquire]: sync.runtime_SemacquireMutex(0xc00026b1cc, 0x0, 0x1) runtime/sema.go:71 +0x47 sync.(*Mutex).lockSlow(0xc00026b1c8) sync/mutex.go:138 +0x105 sync.(*Mutex).Lock(...) sync/mutex.go:81 sync.(*RWMutex).Lock(0xc00026b1c8) sync/rwmutex.go:111 +0x90 github.com/tendermint/tendermint/internal/statesync.(*Reactor).processPeerUpdate(0xc00026b080, 0xc000650008, 0x28, 0x124de90, 0x4) github.com/tendermint/tendermint/internal/statesync/reactor.go:849 +0x1a5 github.com/tendermint/tendermint/internal/statesync.(*Reactor).processPeerUpdates(0xc00026b080) github.com/tendermint/tendermint/internal/statesync/reactor.go:883 +0xab created by github.com/tendermint/tendermint/internal/statesync.(*Reactor.OnStart github.com/tendermint/tendermint/internal/statesync/reactor.go:219 +0xcd) ```
3 years ago
  1. package statesync
  2. import (
  3. "context"
  4. "errors"
  5. "sync"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/mock"
  10. "github.com/stretchr/testify/require"
  11. abci "github.com/tendermint/tendermint/abci/types"
  12. "github.com/tendermint/tendermint/internal/proxy"
  13. proxymocks "github.com/tendermint/tendermint/internal/proxy/mocks"
  14. sm "github.com/tendermint/tendermint/internal/state"
  15. "github.com/tendermint/tendermint/internal/statesync/mocks"
  16. ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
  17. "github.com/tendermint/tendermint/types"
  18. "github.com/tendermint/tendermint/version"
  19. )
  20. func TestSyncer_SyncAny(t *testing.T) {
  21. ctx, cancel := context.WithCancel(context.Background())
  22. defer cancel()
  23. state := sm.State{
  24. ChainID: "chain",
  25. Version: sm.Version{
  26. Consensus: version.Consensus{
  27. Block: version.BlockProtocol,
  28. App: 0,
  29. },
  30. Software: version.TMVersion,
  31. },
  32. LastBlockHeight: 1,
  33. LastBlockID: types.BlockID{Hash: []byte("blockhash")},
  34. LastBlockTime: time.Now(),
  35. LastResultsHash: []byte("last_results_hash"),
  36. AppHash: []byte("app_hash"),
  37. LastValidators: &types.ValidatorSet{Proposer: &types.Validator{Address: []byte("val1")}},
  38. Validators: &types.ValidatorSet{Proposer: &types.Validator{Address: []byte("val2")}},
  39. NextValidators: &types.ValidatorSet{Proposer: &types.Validator{Address: []byte("val3")}},
  40. ConsensusParams: *types.DefaultConsensusParams(),
  41. LastHeightConsensusParamsChanged: 1,
  42. }
  43. commit := &types.Commit{BlockID: types.BlockID{Hash: []byte("blockhash")}}
  44. chunks := []*chunk{
  45. {Height: 1, Format: 1, Index: 0, Chunk: []byte{1, 1, 0}},
  46. {Height: 1, Format: 1, Index: 1, Chunk: []byte{1, 1, 1}},
  47. {Height: 1, Format: 1, Index: 2, Chunk: []byte{1, 1, 2}},
  48. }
  49. s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  50. stateProvider := &mocks.StateProvider{}
  51. stateProvider.On("AppHash", mock.Anything, uint64(1)).Return(state.AppHash, nil)
  52. stateProvider.On("AppHash", mock.Anything, uint64(2)).Return([]byte("app_hash_2"), nil)
  53. stateProvider.On("Commit", mock.Anything, uint64(1)).Return(commit, nil)
  54. stateProvider.On("State", mock.Anything, uint64(1)).Return(state, nil)
  55. connSnapshot := &proxymocks.AppConnSnapshot{}
  56. connQuery := &proxymocks.AppConnQuery{}
  57. peerAID := types.NodeID("aa")
  58. peerBID := types.NodeID("bb")
  59. peerCID := types.NodeID("cc")
  60. rts := setup(ctx, t, connSnapshot, connQuery, stateProvider, 4)
  61. rts.reactor.syncer = rts.syncer
  62. // Adding a chunk should error when no sync is in progress
  63. _, err := rts.syncer.AddChunk(&chunk{Height: 1, Format: 1, Index: 0, Chunk: []byte{1}})
  64. require.Error(t, err)
  65. // Adding a couple of peers should trigger snapshot discovery messages
  66. err = rts.syncer.AddPeer(ctx, peerAID)
  67. require.NoError(t, err)
  68. e := <-rts.snapshotOutCh
  69. require.Equal(t, &ssproto.SnapshotsRequest{}, e.Message)
  70. require.Equal(t, peerAID, e.To)
  71. err = rts.syncer.AddPeer(ctx, peerBID)
  72. require.NoError(t, err)
  73. e = <-rts.snapshotOutCh
  74. require.Equal(t, &ssproto.SnapshotsRequest{}, e.Message)
  75. require.Equal(t, peerBID, e.To)
  76. // Both peers report back with snapshots. One of them also returns a snapshot we don't want, in
  77. // format 2, which will be rejected by the ABCI application.
  78. new, err := rts.syncer.AddSnapshot(peerAID, s)
  79. require.NoError(t, err)
  80. require.True(t, new)
  81. new, err = rts.syncer.AddSnapshot(peerBID, s)
  82. require.NoError(t, err)
  83. require.False(t, new)
  84. s2 := &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1}}
  85. new, err = rts.syncer.AddSnapshot(peerBID, s2)
  86. require.NoError(t, err)
  87. require.True(t, new)
  88. new, err = rts.syncer.AddSnapshot(peerCID, s2)
  89. require.NoError(t, err)
  90. require.False(t, new)
  91. // We start a sync, with peers sending back chunks when requested. We first reject the snapshot
  92. // with height 2 format 2, and accept the snapshot at height 1.
  93. connSnapshot.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  94. Snapshot: &abci.Snapshot{
  95. Height: 2,
  96. Format: 2,
  97. Chunks: 3,
  98. Hash: []byte{1},
  99. },
  100. AppHash: []byte("app_hash_2"),
  101. }).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil)
  102. connSnapshot.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  103. Snapshot: &abci.Snapshot{
  104. Height: s.Height,
  105. Format: s.Format,
  106. Chunks: s.Chunks,
  107. Hash: s.Hash,
  108. Metadata: s.Metadata,
  109. },
  110. AppHash: []byte("app_hash"),
  111. }).Times(2).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil)
  112. chunkRequests := make(map[uint32]int)
  113. chunkRequestsMtx := sync.Mutex{}
  114. chunkProcessDone := make(chan struct{})
  115. go func() {
  116. defer close(chunkProcessDone)
  117. var seen int
  118. for {
  119. if seen >= 4 {
  120. return
  121. }
  122. select {
  123. case <-ctx.Done():
  124. t.Logf("sent %d chunks", seen)
  125. return
  126. case e := <-rts.chunkOutCh:
  127. msg, ok := e.Message.(*ssproto.ChunkRequest)
  128. assert.True(t, ok)
  129. assert.EqualValues(t, 1, msg.Height)
  130. assert.EqualValues(t, 1, msg.Format)
  131. assert.LessOrEqual(t, msg.Index, uint32(len(chunks)))
  132. added, err := rts.syncer.AddChunk(chunks[msg.Index])
  133. assert.NoError(t, err)
  134. assert.True(t, added)
  135. chunkRequestsMtx.Lock()
  136. chunkRequests[msg.Index]++
  137. chunkRequestsMtx.Unlock()
  138. seen++
  139. t.Logf("added chunk (%d of 4): %d", seen, msg.Index)
  140. }
  141. }
  142. }()
  143. // The first time we're applying chunk 2 we tell it to retry the snapshot and discard chunk 1,
  144. // which should cause it to keep the existing chunk 0 and 2, and restart restoration from
  145. // beginning. We also wait for a little while, to exercise the retry logic in fetchChunks().
  146. connSnapshot.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  147. Index: 2, Chunk: []byte{1, 1, 2},
  148. }).Once().Run(func(args mock.Arguments) { time.Sleep(2 * time.Second) }).Return(
  149. &abci.ResponseApplySnapshotChunk{
  150. Result: abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT,
  151. RefetchChunks: []uint32{1},
  152. }, nil)
  153. connSnapshot.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  154. Index: 0, Chunk: []byte{1, 1, 0},
  155. }).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  156. connSnapshot.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  157. Index: 1, Chunk: []byte{1, 1, 1},
  158. }).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  159. connSnapshot.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  160. Index: 2, Chunk: []byte{1, 1, 2},
  161. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  162. connQuery.On("Info", mock.Anything, proxy.RequestInfo).Return(&abci.ResponseInfo{
  163. AppVersion: 9,
  164. LastBlockHeight: 1,
  165. LastBlockAppHash: []byte("app_hash"),
  166. }, nil)
  167. newState, lastCommit, err := rts.syncer.SyncAny(ctx, 0, func() error { return nil })
  168. require.NoError(t, err)
  169. <-chunkProcessDone
  170. chunkRequestsMtx.Lock()
  171. require.Equal(t, map[uint32]int{0: 1, 1: 2, 2: 1}, chunkRequests)
  172. chunkRequestsMtx.Unlock()
  173. // The syncer should have updated the state app version from the ABCI info response.
  174. expectState := state
  175. expectState.Version.Consensus.App = 9
  176. require.Equal(t, expectState, newState)
  177. require.Equal(t, commit, lastCommit)
  178. require.Equal(t, len(chunks), int(rts.syncer.processingSnapshot.Chunks))
  179. require.Equal(t, expectState.LastBlockHeight, rts.syncer.lastSyncedSnapshotHeight)
  180. require.True(t, rts.syncer.avgChunkTime > 0)
  181. require.Equal(t, int64(rts.syncer.processingSnapshot.Chunks), rts.reactor.SnapshotChunksTotal())
  182. require.Equal(t, rts.syncer.lastSyncedSnapshotHeight, rts.reactor.SnapshotHeight())
  183. require.Equal(t, time.Duration(rts.syncer.avgChunkTime), rts.reactor.ChunkProcessAvgTime())
  184. require.Equal(t, int64(len(rts.syncer.snapshots.snapshots)), rts.reactor.TotalSnapshots())
  185. require.Equal(t, int64(0), rts.reactor.SnapshotChunksCount())
  186. connSnapshot.AssertExpectations(t)
  187. connQuery.AssertExpectations(t)
  188. }
  189. func TestSyncer_SyncAny_noSnapshots(t *testing.T) {
  190. stateProvider := &mocks.StateProvider{}
  191. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  192. ctx, cancel := context.WithCancel(context.Background())
  193. defer cancel()
  194. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  195. _, _, err := rts.syncer.SyncAny(ctx, 0, func() error { return nil })
  196. require.Equal(t, errNoSnapshots, err)
  197. }
  198. func TestSyncer_SyncAny_abort(t *testing.T) {
  199. stateProvider := &mocks.StateProvider{}
  200. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  201. ctx, cancel := context.WithCancel(context.Background())
  202. defer cancel()
  203. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  204. s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  205. peerID := types.NodeID("aa")
  206. _, err := rts.syncer.AddSnapshot(peerID, s)
  207. require.NoError(t, err)
  208. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  209. Snapshot: toABCI(s), AppHash: []byte("app_hash"),
  210. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil)
  211. _, _, err = rts.syncer.SyncAny(ctx, 0, func() error { return nil })
  212. require.Equal(t, errAbort, err)
  213. rts.conn.AssertExpectations(t)
  214. }
  215. func TestSyncer_SyncAny_reject(t *testing.T) {
  216. stateProvider := &mocks.StateProvider{}
  217. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  218. ctx, cancel := context.WithCancel(context.Background())
  219. defer cancel()
  220. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  221. // s22 is tried first, then s12, then s11, then errNoSnapshots
  222. s22 := &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
  223. s12 := &snapshot{Height: 1, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
  224. s11 := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  225. peerID := types.NodeID("aa")
  226. _, err := rts.syncer.AddSnapshot(peerID, s22)
  227. require.NoError(t, err)
  228. _, err = rts.syncer.AddSnapshot(peerID, s12)
  229. require.NoError(t, err)
  230. _, err = rts.syncer.AddSnapshot(peerID, s11)
  231. require.NoError(t, err)
  232. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  233. Snapshot: toABCI(s22), AppHash: []byte("app_hash"),
  234. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
  235. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  236. Snapshot: toABCI(s12), AppHash: []byte("app_hash"),
  237. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
  238. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  239. Snapshot: toABCI(s11), AppHash: []byte("app_hash"),
  240. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
  241. _, _, err = rts.syncer.SyncAny(ctx, 0, func() error { return nil })
  242. require.Equal(t, errNoSnapshots, err)
  243. rts.conn.AssertExpectations(t)
  244. }
  245. func TestSyncer_SyncAny_reject_format(t *testing.T) {
  246. stateProvider := &mocks.StateProvider{}
  247. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  248. ctx, cancel := context.WithCancel(context.Background())
  249. defer cancel()
  250. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  251. // s22 is tried first, which reject s22 and s12, then s11 will abort.
  252. s22 := &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
  253. s12 := &snapshot{Height: 1, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
  254. s11 := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  255. peerID := types.NodeID("aa")
  256. _, err := rts.syncer.AddSnapshot(peerID, s22)
  257. require.NoError(t, err)
  258. _, err = rts.syncer.AddSnapshot(peerID, s12)
  259. require.NoError(t, err)
  260. _, err = rts.syncer.AddSnapshot(peerID, s11)
  261. require.NoError(t, err)
  262. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  263. Snapshot: toABCI(s22), AppHash: []byte("app_hash"),
  264. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil)
  265. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  266. Snapshot: toABCI(s11), AppHash: []byte("app_hash"),
  267. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil)
  268. _, _, err = rts.syncer.SyncAny(ctx, 0, func() error { return nil })
  269. require.Equal(t, errAbort, err)
  270. rts.conn.AssertExpectations(t)
  271. }
  272. func TestSyncer_SyncAny_reject_sender(t *testing.T) {
  273. stateProvider := &mocks.StateProvider{}
  274. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  275. ctx, cancel := context.WithCancel(context.Background())
  276. defer cancel()
  277. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  278. peerAID := types.NodeID("aa")
  279. peerBID := types.NodeID("bb")
  280. peerCID := types.NodeID("cc")
  281. // sbc will be offered first, which will be rejected with reject_sender, causing all snapshots
  282. // submitted by both b and c (i.e. sb, sc, sbc) to be rejected. Finally, sa will reject and
  283. // errNoSnapshots is returned.
  284. sa := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  285. sb := &snapshot{Height: 2, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  286. sc := &snapshot{Height: 3, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  287. sbc := &snapshot{Height: 4, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  288. _, err := rts.syncer.AddSnapshot(peerAID, sa)
  289. require.NoError(t, err)
  290. _, err = rts.syncer.AddSnapshot(peerBID, sb)
  291. require.NoError(t, err)
  292. _, err = rts.syncer.AddSnapshot(peerCID, sc)
  293. require.NoError(t, err)
  294. _, err = rts.syncer.AddSnapshot(peerBID, sbc)
  295. require.NoError(t, err)
  296. _, err = rts.syncer.AddSnapshot(peerCID, sbc)
  297. require.NoError(t, err)
  298. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  299. Snapshot: toABCI(sbc), AppHash: []byte("app_hash"),
  300. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_SENDER}, nil)
  301. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  302. Snapshot: toABCI(sa), AppHash: []byte("app_hash"),
  303. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
  304. _, _, err = rts.syncer.SyncAny(ctx, 0, func() error { return nil })
  305. require.Equal(t, errNoSnapshots, err)
  306. rts.conn.AssertExpectations(t)
  307. }
  308. func TestSyncer_SyncAny_abciError(t *testing.T) {
  309. stateProvider := &mocks.StateProvider{}
  310. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  311. ctx, cancel := context.WithCancel(context.Background())
  312. defer cancel()
  313. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  314. errBoom := errors.New("boom")
  315. s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  316. peerID := types.NodeID("aa")
  317. _, err := rts.syncer.AddSnapshot(peerID, s)
  318. require.NoError(t, err)
  319. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  320. Snapshot: toABCI(s), AppHash: []byte("app_hash"),
  321. }).Once().Return(nil, errBoom)
  322. _, _, err = rts.syncer.SyncAny(ctx, 0, func() error { return nil })
  323. require.True(t, errors.Is(err, errBoom))
  324. rts.conn.AssertExpectations(t)
  325. }
  326. func TestSyncer_offerSnapshot(t *testing.T) {
  327. unknownErr := errors.New("unknown error")
  328. boom := errors.New("boom")
  329. testcases := map[string]struct {
  330. result abci.ResponseOfferSnapshot_Result
  331. err error
  332. expectErr error
  333. }{
  334. "accept": {abci.ResponseOfferSnapshot_ACCEPT, nil, nil},
  335. "abort": {abci.ResponseOfferSnapshot_ABORT, nil, errAbort},
  336. "reject": {abci.ResponseOfferSnapshot_REJECT, nil, errRejectSnapshot},
  337. "reject_format": {abci.ResponseOfferSnapshot_REJECT_FORMAT, nil, errRejectFormat},
  338. "reject_sender": {abci.ResponseOfferSnapshot_REJECT_SENDER, nil, errRejectSender},
  339. "unknown": {abci.ResponseOfferSnapshot_UNKNOWN, nil, unknownErr},
  340. "error": {0, boom, boom},
  341. "unknown non-zero": {9, nil, unknownErr},
  342. }
  343. ctx, cancel := context.WithCancel(context.Background())
  344. defer cancel()
  345. for name, tc := range testcases {
  346. tc := tc
  347. t.Run(name, func(t *testing.T) {
  348. ctx, cancel := context.WithCancel(ctx)
  349. defer cancel()
  350. stateProvider := &mocks.StateProvider{}
  351. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  352. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  353. s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")}
  354. rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
  355. Snapshot: toABCI(s),
  356. AppHash: []byte("app_hash"),
  357. }).Return(&abci.ResponseOfferSnapshot{Result: tc.result}, tc.err)
  358. err := rts.syncer.offerSnapshot(ctx, s)
  359. if tc.expectErr == unknownErr {
  360. require.Error(t, err)
  361. } else {
  362. unwrapped := errors.Unwrap(err)
  363. if unwrapped != nil {
  364. err = unwrapped
  365. }
  366. require.Equal(t, tc.expectErr, err)
  367. }
  368. })
  369. }
  370. }
  371. func TestSyncer_applyChunks_Results(t *testing.T) {
  372. unknownErr := errors.New("unknown error")
  373. boom := errors.New("boom")
  374. testcases := map[string]struct {
  375. result abci.ResponseApplySnapshotChunk_Result
  376. err error
  377. expectErr error
  378. }{
  379. "accept": {abci.ResponseApplySnapshotChunk_ACCEPT, nil, nil},
  380. "abort": {abci.ResponseApplySnapshotChunk_ABORT, nil, errAbort},
  381. "retry": {abci.ResponseApplySnapshotChunk_RETRY, nil, nil},
  382. "retry_snapshot": {abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT, nil, errRetrySnapshot},
  383. "reject_snapshot": {abci.ResponseApplySnapshotChunk_REJECT_SNAPSHOT, nil, errRejectSnapshot},
  384. "unknown": {abci.ResponseApplySnapshotChunk_UNKNOWN, nil, unknownErr},
  385. "error": {0, boom, boom},
  386. "unknown non-zero": {9, nil, unknownErr},
  387. }
  388. ctx, cancel := context.WithCancel(context.Background())
  389. defer cancel()
  390. for name, tc := range testcases {
  391. tc := tc
  392. t.Run(name, func(t *testing.T) {
  393. ctx, cancel := context.WithCancel(ctx)
  394. defer cancel()
  395. stateProvider := &mocks.StateProvider{}
  396. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  397. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  398. body := []byte{1, 2, 3}
  399. chunks, err := newChunkQueue(&snapshot{Height: 1, Format: 1, Chunks: 1}, t.TempDir())
  400. require.NoError(t, err)
  401. fetchStartTime := time.Now()
  402. _, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: body})
  403. require.NoError(t, err)
  404. rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  405. Index: 0, Chunk: body,
  406. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: tc.result}, tc.err)
  407. if tc.result == abci.ResponseApplySnapshotChunk_RETRY {
  408. rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  409. Index: 0, Chunk: body,
  410. }).Once().Return(&abci.ResponseApplySnapshotChunk{
  411. Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  412. }
  413. err = rts.syncer.applyChunks(ctx, chunks, fetchStartTime)
  414. if tc.expectErr == unknownErr {
  415. require.Error(t, err)
  416. } else {
  417. unwrapped := errors.Unwrap(err)
  418. if unwrapped != nil {
  419. err = unwrapped
  420. }
  421. require.Equal(t, tc.expectErr, err)
  422. }
  423. rts.conn.AssertExpectations(t)
  424. })
  425. }
  426. }
  427. func TestSyncer_applyChunks_RefetchChunks(t *testing.T) {
  428. // Discarding chunks via refetch_chunks should work the same for all results
  429. testcases := map[string]struct {
  430. result abci.ResponseApplySnapshotChunk_Result
  431. }{
  432. "accept": {abci.ResponseApplySnapshotChunk_ACCEPT},
  433. "abort": {abci.ResponseApplySnapshotChunk_ABORT},
  434. "retry": {abci.ResponseApplySnapshotChunk_RETRY},
  435. "retry_snapshot": {abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT},
  436. "reject_snapshot": {abci.ResponseApplySnapshotChunk_REJECT_SNAPSHOT},
  437. }
  438. ctx, cancel := context.WithCancel(context.Background())
  439. defer cancel()
  440. for name, tc := range testcases {
  441. tc := tc
  442. t.Run(name, func(t *testing.T) {
  443. ctx, cancel := context.WithCancel(ctx)
  444. defer cancel()
  445. stateProvider := &mocks.StateProvider{}
  446. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  447. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  448. chunks, err := newChunkQueue(&snapshot{Height: 1, Format: 1, Chunks: 3}, t.TempDir())
  449. require.NoError(t, err)
  450. fetchStartTime := time.Now()
  451. added, err := chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: []byte{0}})
  452. require.True(t, added)
  453. require.NoError(t, err)
  454. added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 1, Chunk: []byte{1}})
  455. require.True(t, added)
  456. require.NoError(t, err)
  457. added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 2, Chunk: []byte{2}})
  458. require.True(t, added)
  459. require.NoError(t, err)
  460. // The first two chunks are accepted, before the last one asks for 1 to be refetched
  461. rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  462. Index: 0, Chunk: []byte{0},
  463. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  464. rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  465. Index: 1, Chunk: []byte{1},
  466. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  467. rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  468. Index: 2, Chunk: []byte{2},
  469. }).Once().Return(&abci.ResponseApplySnapshotChunk{
  470. Result: tc.result,
  471. RefetchChunks: []uint32{1},
  472. }, nil)
  473. // Since removing the chunk will cause Next() to block, we spawn a goroutine, then
  474. // check the queue contents, and finally close the queue to end the goroutine.
  475. // We don't really care about the result of applyChunks, since it has separate test.
  476. go func() {
  477. rts.syncer.applyChunks(ctx, chunks, fetchStartTime) //nolint:errcheck // purposefully ignore error
  478. }()
  479. time.Sleep(50 * time.Millisecond)
  480. require.True(t, chunks.Has(0))
  481. require.False(t, chunks.Has(1))
  482. require.True(t, chunks.Has(2))
  483. require.NoError(t, chunks.Close())
  484. })
  485. }
  486. }
  487. func TestSyncer_applyChunks_RejectSenders(t *testing.T) {
  488. // Banning chunks senders via ban_chunk_senders should work the same for all results
  489. testcases := map[string]struct {
  490. result abci.ResponseApplySnapshotChunk_Result
  491. }{
  492. "accept": {abci.ResponseApplySnapshotChunk_ACCEPT},
  493. "abort": {abci.ResponseApplySnapshotChunk_ABORT},
  494. "retry": {abci.ResponseApplySnapshotChunk_RETRY},
  495. "retry_snapshot": {abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT},
  496. "reject_snapshot": {abci.ResponseApplySnapshotChunk_REJECT_SNAPSHOT},
  497. }
  498. ctx, cancel := context.WithCancel(context.Background())
  499. defer cancel()
  500. for name, tc := range testcases {
  501. tc := tc
  502. t.Run(name, func(t *testing.T) {
  503. ctx, cancel := context.WithCancel(ctx)
  504. defer cancel()
  505. stateProvider := &mocks.StateProvider{}
  506. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  507. rts := setup(ctx, t, nil, nil, stateProvider, 2)
  508. // Set up three peers across two snapshots, and ask for one of them to be banned.
  509. // It should be banned from all snapshots.
  510. peerAID := types.NodeID("aa")
  511. peerBID := types.NodeID("bb")
  512. peerCID := types.NodeID("cc")
  513. s1 := &snapshot{Height: 1, Format: 1, Chunks: 3}
  514. s2 := &snapshot{Height: 2, Format: 1, Chunks: 3}
  515. _, err := rts.syncer.AddSnapshot(peerAID, s1)
  516. require.NoError(t, err)
  517. _, err = rts.syncer.AddSnapshot(peerAID, s2)
  518. require.NoError(t, err)
  519. _, err = rts.syncer.AddSnapshot(peerBID, s1)
  520. require.NoError(t, err)
  521. _, err = rts.syncer.AddSnapshot(peerBID, s2)
  522. require.NoError(t, err)
  523. _, err = rts.syncer.AddSnapshot(peerCID, s1)
  524. require.NoError(t, err)
  525. _, err = rts.syncer.AddSnapshot(peerCID, s2)
  526. require.NoError(t, err)
  527. chunks, err := newChunkQueue(s1, t.TempDir())
  528. require.NoError(t, err)
  529. fetchStartTime := time.Now()
  530. added, err := chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: []byte{0}, Sender: peerAID})
  531. require.True(t, added)
  532. require.NoError(t, err)
  533. added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 1, Chunk: []byte{1}, Sender: peerBID})
  534. require.True(t, added)
  535. require.NoError(t, err)
  536. added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 2, Chunk: []byte{2}, Sender: peerCID})
  537. require.True(t, added)
  538. require.NoError(t, err)
  539. // The first two chunks are accepted, before the last one asks for b sender to be rejected
  540. rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  541. Index: 0, Chunk: []byte{0}, Sender: "aa",
  542. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  543. rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  544. Index: 1, Chunk: []byte{1}, Sender: "bb",
  545. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  546. rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  547. Index: 2, Chunk: []byte{2}, Sender: "cc",
  548. }).Once().Return(&abci.ResponseApplySnapshotChunk{
  549. Result: tc.result,
  550. RejectSenders: []string{string(peerBID)},
  551. }, nil)
  552. // On retry, the last chunk will be tried again, so we just accept it then.
  553. if tc.result == abci.ResponseApplySnapshotChunk_RETRY {
  554. rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
  555. Index: 2, Chunk: []byte{2}, Sender: "cc",
  556. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  557. }
  558. // We don't really care about the result of applyChunks, since it has separate test.
  559. // However, it will block on e.g. retry result, so we spawn a goroutine that will
  560. // be shut down when the chunk queue closes.
  561. go func() {
  562. rts.syncer.applyChunks(ctx, chunks, fetchStartTime) //nolint:errcheck // purposefully ignore error
  563. }()
  564. time.Sleep(50 * time.Millisecond)
  565. s1peers := rts.syncer.snapshots.GetPeers(s1)
  566. require.Len(t, s1peers, 2)
  567. require.EqualValues(t, "aa", s1peers[0])
  568. require.EqualValues(t, "cc", s1peers[1])
  569. rts.syncer.snapshots.GetPeers(s1)
  570. require.Len(t, s1peers, 2)
  571. require.EqualValues(t, "aa", s1peers[0])
  572. require.EqualValues(t, "cc", s1peers[1])
  573. require.NoError(t, chunks.Close())
  574. })
  575. }
  576. }
  577. func TestSyncer_verifyApp(t *testing.T) {
  578. boom := errors.New("boom")
  579. s := &snapshot{Height: 3, Format: 1, Chunks: 5, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")}
  580. testcases := map[string]struct {
  581. response *abci.ResponseInfo
  582. err error
  583. expectErr error
  584. }{
  585. "verified": {&abci.ResponseInfo{
  586. LastBlockHeight: 3,
  587. LastBlockAppHash: []byte("app_hash"),
  588. AppVersion: 9,
  589. }, nil, nil},
  590. "invalid height": {&abci.ResponseInfo{
  591. LastBlockHeight: 5,
  592. LastBlockAppHash: []byte("app_hash"),
  593. AppVersion: 9,
  594. }, nil, errVerifyFailed},
  595. "invalid hash": {&abci.ResponseInfo{
  596. LastBlockHeight: 3,
  597. LastBlockAppHash: []byte("xxx"),
  598. AppVersion: 9,
  599. }, nil, errVerifyFailed},
  600. "error": {nil, boom, boom},
  601. }
  602. ctx, cancel := context.WithCancel(context.Background())
  603. defer cancel()
  604. for name, tc := range testcases {
  605. tc := tc
  606. t.Run(name, func(t *testing.T) {
  607. ctx, cancel := context.WithCancel(ctx)
  608. defer cancel()
  609. rts := setup(ctx, t, nil, nil, nil, 2)
  610. rts.connQuery.On("Info", mock.Anything, proxy.RequestInfo).Return(tc.response, tc.err)
  611. version, err := rts.syncer.verifyApp(ctx, s)
  612. unwrapped := errors.Unwrap(err)
  613. if unwrapped != nil {
  614. err = unwrapped
  615. }
  616. require.Equal(t, tc.expectErr, err)
  617. if err == nil {
  618. require.Equal(t, tc.response.AppVersion, version)
  619. }
  620. })
  621. }
  622. }
  623. func toABCI(s *snapshot) *abci.Snapshot {
  624. return &abci.Snapshot{
  625. Height: s.Height,
  626. Format: s.Format,
  627. Chunks: s.Chunks,
  628. Hash: s.Hash,
  629. Metadata: s.Metadata,
  630. }
  631. }