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.

664 lines
25 KiB

  1. package statesync
  2. import (
  3. "context"
  4. "errors"
  5. "testing"
  6. "time"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/mock"
  9. "github.com/stretchr/testify/require"
  10. abci "github.com/tendermint/tendermint/abci/types"
  11. "github.com/tendermint/tendermint/libs/log"
  12. tmsync "github.com/tendermint/tendermint/libs/sync"
  13. "github.com/tendermint/tendermint/p2p"
  14. p2pmocks "github.com/tendermint/tendermint/p2p/mocks"
  15. tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
  16. ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
  17. tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
  18. "github.com/tendermint/tendermint/proxy"
  19. proxymocks "github.com/tendermint/tendermint/proxy/mocks"
  20. sm "github.com/tendermint/tendermint/state"
  21. "github.com/tendermint/tendermint/statesync/mocks"
  22. "github.com/tendermint/tendermint/types"
  23. "github.com/tendermint/tendermint/version"
  24. )
  25. var ctx = context.Background()
  26. // Sets up a basic syncer that can be used to test OfferSnapshot requests
  27. func setupOfferSyncer(t *testing.T) (*syncer, *proxymocks.AppConnSnapshot) {
  28. connQuery := &proxymocks.AppConnQuery{}
  29. connSnapshot := &proxymocks.AppConnSnapshot{}
  30. stateProvider := &mocks.StateProvider{}
  31. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  32. syncer := newSyncer(log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
  33. return syncer, connSnapshot
  34. }
  35. // Sets up a simple peer mock with an ID
  36. func simplePeer(id string) *p2pmocks.Peer {
  37. peer := &p2pmocks.Peer{}
  38. peer.On("ID").Return(p2p.ID(id))
  39. return peer
  40. }
  41. func TestSyncer_SyncAny(t *testing.T) {
  42. state := sm.State{
  43. ChainID: "chain",
  44. Version: tmstate.Version{
  45. Consensus: tmversion.Consensus{
  46. Block: version.BlockProtocol,
  47. App: 0,
  48. },
  49. Software: version.TMCoreSemVer,
  50. },
  51. LastBlockHeight: 1,
  52. LastBlockID: types.BlockID{Hash: []byte("blockhash")},
  53. LastBlockTime: time.Now(),
  54. LastResultsHash: []byte("last_results_hash"),
  55. AppHash: []byte("app_hash"),
  56. LastValidators: &types.ValidatorSet{Proposer: &types.Validator{Address: []byte("val1")}},
  57. Validators: &types.ValidatorSet{Proposer: &types.Validator{Address: []byte("val2")}},
  58. NextValidators: &types.ValidatorSet{Proposer: &types.Validator{Address: []byte("val3")}},
  59. ConsensusParams: *types.DefaultConsensusParams(),
  60. LastHeightConsensusParamsChanged: 1,
  61. }
  62. commit := &types.Commit{BlockID: types.BlockID{Hash: []byte("blockhash")}}
  63. chunks := []*chunk{
  64. {Height: 1, Format: 1, Index: 0, Chunk: []byte{1, 1, 0}},
  65. {Height: 1, Format: 1, Index: 1, Chunk: []byte{1, 1, 1}},
  66. {Height: 1, Format: 1, Index: 2, Chunk: []byte{1, 1, 2}},
  67. }
  68. s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  69. stateProvider := &mocks.StateProvider{}
  70. stateProvider.On("AppHash", mock.Anything, uint64(1)).Return(state.AppHash, nil)
  71. stateProvider.On("AppHash", mock.Anything, uint64(2)).Return([]byte("app_hash_2"), nil)
  72. stateProvider.On("Commit", mock.Anything, uint64(1)).Return(commit, nil)
  73. stateProvider.On("State", mock.Anything, uint64(1)).Return(state, nil)
  74. connSnapshot := &proxymocks.AppConnSnapshot{}
  75. connQuery := &proxymocks.AppConnQuery{}
  76. syncer := newSyncer(log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
  77. // Adding a chunk should error when no sync is in progress
  78. _, err := syncer.AddChunk(&chunk{Height: 1, Format: 1, Index: 0, Chunk: []byte{1}})
  79. require.Error(t, err)
  80. // Adding a couple of peers should trigger snapshot discovery messages
  81. peerA := &p2pmocks.Peer{}
  82. peerA.On("ID").Return(p2p.ID("a"))
  83. peerA.On("Send", SnapshotChannel, mustEncodeMsg(&ssproto.SnapshotsRequest{})).Return(true)
  84. syncer.AddPeer(peerA)
  85. peerA.AssertExpectations(t)
  86. peerB := &p2pmocks.Peer{}
  87. peerB.On("ID").Return(p2p.ID("b"))
  88. peerB.On("Send", SnapshotChannel, mustEncodeMsg(&ssproto.SnapshotsRequest{})).Return(true)
  89. syncer.AddPeer(peerB)
  90. peerB.AssertExpectations(t)
  91. // Both peers report back with snapshots. One of them also returns a snapshot we don't want, in
  92. // format 2, which will be rejected by the ABCI application.
  93. new, err := syncer.AddSnapshot(peerA, s)
  94. require.NoError(t, err)
  95. assert.True(t, new)
  96. new, err = syncer.AddSnapshot(peerB, s)
  97. require.NoError(t, err)
  98. assert.False(t, new)
  99. new, err = syncer.AddSnapshot(peerB, &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1}})
  100. require.NoError(t, err)
  101. assert.True(t, new)
  102. // We start a sync, with peers sending back chunks when requested. We first reject the snapshot
  103. // with height 2 format 2, and accept the snapshot at height 1.
  104. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  105. Snapshot: &abci.Snapshot{
  106. Height: 2,
  107. Format: 2,
  108. Chunks: 3,
  109. Hash: []byte{1},
  110. },
  111. AppHash: []byte("app_hash_2"),
  112. }).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil)
  113. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  114. Snapshot: &abci.Snapshot{
  115. Height: s.Height,
  116. Format: s.Format,
  117. Chunks: s.Chunks,
  118. Hash: s.Hash,
  119. Metadata: s.Metadata,
  120. },
  121. AppHash: []byte("app_hash"),
  122. }).Times(2).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil)
  123. chunkRequests := make(map[uint32]int)
  124. chunkRequestsMtx := tmsync.Mutex{}
  125. onChunkRequest := func(args mock.Arguments) {
  126. pb, err := decodeMsg(args[1].([]byte))
  127. assert.NoError(t, err)
  128. msg := pb.(*ssproto.ChunkRequest)
  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 := 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. }
  139. peerA.On("Send", ChunkChannel, mock.Anything).Maybe().Run(onChunkRequest).Return(true)
  140. peerB.On("Send", ChunkChannel, mock.Anything).Maybe().Run(onChunkRequest).Return(true)
  141. // The first time we're applying chunk 2 we tell it to retry the snapshot and discard chunk 1,
  142. // which should cause it to keep the existing chunk 0 and 2, and restart restoration from
  143. // beginning. We also wait for a little while, to exercise the retry logic in fetchChunks().
  144. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  145. Index: 2, Chunk: []byte{1, 1, 2},
  146. }).Once().Run(func(args mock.Arguments) { time.Sleep(2 * time.Second) }).Return(
  147. &abci.ResponseApplySnapshotChunk{
  148. Result: abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT,
  149. RefetchChunks: []uint32{1},
  150. }, nil)
  151. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  152. Index: 0, Chunk: []byte{1, 1, 0},
  153. }).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  154. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  155. Index: 1, Chunk: []byte{1, 1, 1},
  156. }).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  157. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  158. Index: 2, Chunk: []byte{1, 1, 2},
  159. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  160. connQuery.On("InfoSync", ctx, proxy.RequestInfo).Return(&abci.ResponseInfo{
  161. AppVersion: 9,
  162. LastBlockHeight: 1,
  163. LastBlockAppHash: []byte("app_hash"),
  164. }, nil)
  165. newState, lastCommit, err := syncer.SyncAny(0)
  166. require.NoError(t, err)
  167. time.Sleep(50 * time.Millisecond) // wait for peers to receive requests
  168. chunkRequestsMtx.Lock()
  169. assert.Equal(t, map[uint32]int{0: 1, 1: 2, 2: 1}, chunkRequests)
  170. chunkRequestsMtx.Unlock()
  171. // The syncer should have updated the state app version from the ABCI info response.
  172. expectState := state
  173. expectState.Version.Consensus.App = 9
  174. assert.Equal(t, expectState, newState)
  175. assert.Equal(t, commit, lastCommit)
  176. connSnapshot.AssertExpectations(t)
  177. connQuery.AssertExpectations(t)
  178. peerA.AssertExpectations(t)
  179. peerB.AssertExpectations(t)
  180. }
  181. func TestSyncer_SyncAny_noSnapshots(t *testing.T) {
  182. syncer, _ := setupOfferSyncer(t)
  183. _, _, err := syncer.SyncAny(0)
  184. assert.Equal(t, errNoSnapshots, err)
  185. }
  186. func TestSyncer_SyncAny_abort(t *testing.T) {
  187. syncer, connSnapshot := setupOfferSyncer(t)
  188. s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  189. _, err := syncer.AddSnapshot(simplePeer("id"), s)
  190. require.NoError(t, err)
  191. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  192. Snapshot: toABCI(s), AppHash: []byte("app_hash"),
  193. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil)
  194. _, _, err = syncer.SyncAny(0)
  195. assert.Equal(t, errAbort, err)
  196. connSnapshot.AssertExpectations(t)
  197. }
  198. func TestSyncer_SyncAny_reject(t *testing.T) {
  199. syncer, connSnapshot := setupOfferSyncer(t)
  200. // s22 is tried first, then s12, then s11, then errNoSnapshots
  201. s22 := &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
  202. s12 := &snapshot{Height: 1, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
  203. s11 := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  204. _, err := syncer.AddSnapshot(simplePeer("id"), s22)
  205. require.NoError(t, err)
  206. _, err = syncer.AddSnapshot(simplePeer("id"), s12)
  207. require.NoError(t, err)
  208. _, err = syncer.AddSnapshot(simplePeer("id"), s11)
  209. require.NoError(t, err)
  210. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  211. Snapshot: toABCI(s22), AppHash: []byte("app_hash"),
  212. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
  213. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  214. Snapshot: toABCI(s12), AppHash: []byte("app_hash"),
  215. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
  216. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  217. Snapshot: toABCI(s11), AppHash: []byte("app_hash"),
  218. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
  219. _, _, err = syncer.SyncAny(0)
  220. assert.Equal(t, errNoSnapshots, err)
  221. connSnapshot.AssertExpectations(t)
  222. }
  223. func TestSyncer_SyncAny_reject_format(t *testing.T) {
  224. syncer, connSnapshot := setupOfferSyncer(t)
  225. // s22 is tried first, which reject s22 and s12, then s11 will abort.
  226. s22 := &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
  227. s12 := &snapshot{Height: 1, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
  228. s11 := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  229. _, err := syncer.AddSnapshot(simplePeer("id"), s22)
  230. require.NoError(t, err)
  231. _, err = syncer.AddSnapshot(simplePeer("id"), s12)
  232. require.NoError(t, err)
  233. _, err = syncer.AddSnapshot(simplePeer("id"), s11)
  234. require.NoError(t, err)
  235. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  236. Snapshot: toABCI(s22), AppHash: []byte("app_hash"),
  237. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil)
  238. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  239. Snapshot: toABCI(s11), AppHash: []byte("app_hash"),
  240. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil)
  241. _, _, err = syncer.SyncAny(0)
  242. assert.Equal(t, errAbort, err)
  243. connSnapshot.AssertExpectations(t)
  244. }
  245. func TestSyncer_SyncAny_reject_sender(t *testing.T) {
  246. syncer, connSnapshot := setupOfferSyncer(t)
  247. peerA := simplePeer("a")
  248. peerB := simplePeer("b")
  249. peerC := simplePeer("c")
  250. // sbc will be offered first, which will be rejected with reject_sender, causing all snapshots
  251. // submitted by both b and c (i.e. sb, sc, sbc) to be rejected. Finally, sa will reject and
  252. // errNoSnapshots is returned.
  253. sa := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  254. sb := &snapshot{Height: 2, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  255. sc := &snapshot{Height: 3, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  256. sbc := &snapshot{Height: 4, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  257. _, err := syncer.AddSnapshot(peerA, sa)
  258. require.NoError(t, err)
  259. _, err = syncer.AddSnapshot(peerB, sb)
  260. require.NoError(t, err)
  261. _, err = syncer.AddSnapshot(peerC, sc)
  262. require.NoError(t, err)
  263. _, err = syncer.AddSnapshot(peerB, sbc)
  264. require.NoError(t, err)
  265. _, err = syncer.AddSnapshot(peerC, sbc)
  266. require.NoError(t, err)
  267. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  268. Snapshot: toABCI(sbc), AppHash: []byte("app_hash"),
  269. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_SENDER}, nil)
  270. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  271. Snapshot: toABCI(sa), AppHash: []byte("app_hash"),
  272. }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil)
  273. _, _, err = syncer.SyncAny(0)
  274. assert.Equal(t, errNoSnapshots, err)
  275. connSnapshot.AssertExpectations(t)
  276. }
  277. func TestSyncer_SyncAny_abciError(t *testing.T) {
  278. syncer, connSnapshot := setupOfferSyncer(t)
  279. errBoom := errors.New("boom")
  280. s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
  281. _, err := syncer.AddSnapshot(simplePeer("id"), s)
  282. require.NoError(t, err)
  283. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  284. Snapshot: toABCI(s), AppHash: []byte("app_hash"),
  285. }).Once().Return(nil, errBoom)
  286. _, _, err = syncer.SyncAny(0)
  287. assert.True(t, errors.Is(err, errBoom))
  288. connSnapshot.AssertExpectations(t)
  289. }
  290. func TestSyncer_offerSnapshot(t *testing.T) {
  291. unknownErr := errors.New("unknown error")
  292. boom := errors.New("boom")
  293. testcases := map[string]struct {
  294. result abci.ResponseOfferSnapshot_Result
  295. err error
  296. expectErr error
  297. }{
  298. "accept": {abci.ResponseOfferSnapshot_ACCEPT, nil, nil},
  299. "abort": {abci.ResponseOfferSnapshot_ABORT, nil, errAbort},
  300. "reject": {abci.ResponseOfferSnapshot_REJECT, nil, errRejectSnapshot},
  301. "reject_format": {abci.ResponseOfferSnapshot_REJECT_FORMAT, nil, errRejectFormat},
  302. "reject_sender": {abci.ResponseOfferSnapshot_REJECT_SENDER, nil, errRejectSender},
  303. "unknown": {abci.ResponseOfferSnapshot_UNKNOWN, nil, unknownErr},
  304. "error": {0, boom, boom},
  305. "unknown non-zero": {9, nil, unknownErr},
  306. }
  307. for name, tc := range testcases {
  308. tc := tc
  309. t.Run(name, func(t *testing.T) {
  310. syncer, connSnapshot := setupOfferSyncer(t)
  311. s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")}
  312. connSnapshot.On("OfferSnapshotSync", ctx, abci.RequestOfferSnapshot{
  313. Snapshot: toABCI(s),
  314. AppHash: []byte("app_hash"),
  315. }).Return(&abci.ResponseOfferSnapshot{Result: tc.result}, tc.err)
  316. err := syncer.offerSnapshot(s)
  317. if tc.expectErr == unknownErr {
  318. require.Error(t, err)
  319. } else {
  320. unwrapped := errors.Unwrap(err)
  321. if unwrapped != nil {
  322. err = unwrapped
  323. }
  324. assert.Equal(t, tc.expectErr, err)
  325. }
  326. })
  327. }
  328. }
  329. func TestSyncer_applyChunks_Results(t *testing.T) {
  330. unknownErr := errors.New("unknown error")
  331. boom := errors.New("boom")
  332. testcases := map[string]struct {
  333. result abci.ResponseApplySnapshotChunk_Result
  334. err error
  335. expectErr error
  336. }{
  337. "accept": {abci.ResponseApplySnapshotChunk_ACCEPT, nil, nil},
  338. "abort": {abci.ResponseApplySnapshotChunk_ABORT, nil, errAbort},
  339. "retry": {abci.ResponseApplySnapshotChunk_RETRY, nil, nil},
  340. "retry_snapshot": {abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT, nil, errRetrySnapshot},
  341. "reject_snapshot": {abci.ResponseApplySnapshotChunk_REJECT_SNAPSHOT, nil, errRejectSnapshot},
  342. "unknown": {abci.ResponseApplySnapshotChunk_UNKNOWN, nil, unknownErr},
  343. "error": {0, boom, boom},
  344. "unknown non-zero": {9, nil, unknownErr},
  345. }
  346. for name, tc := range testcases {
  347. tc := tc
  348. t.Run(name, func(t *testing.T) {
  349. connQuery := &proxymocks.AppConnQuery{}
  350. connSnapshot := &proxymocks.AppConnSnapshot{}
  351. stateProvider := &mocks.StateProvider{}
  352. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  353. syncer := newSyncer(log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
  354. body := []byte{1, 2, 3}
  355. chunks, err := newChunkQueue(&snapshot{Height: 1, Format: 1, Chunks: 1}, "")
  356. require.NoError(t, err)
  357. _, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: body})
  358. require.NoError(t, err)
  359. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  360. Index: 0, Chunk: body,
  361. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: tc.result}, tc.err)
  362. if tc.result == abci.ResponseApplySnapshotChunk_RETRY {
  363. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  364. Index: 0, Chunk: body,
  365. }).Once().Return(&abci.ResponseApplySnapshotChunk{
  366. Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  367. }
  368. err = syncer.applyChunks(chunks)
  369. if tc.expectErr == unknownErr {
  370. require.Error(t, err)
  371. } else {
  372. unwrapped := errors.Unwrap(err)
  373. if unwrapped != nil {
  374. err = unwrapped
  375. }
  376. assert.Equal(t, tc.expectErr, err)
  377. }
  378. connSnapshot.AssertExpectations(t)
  379. })
  380. }
  381. }
  382. func TestSyncer_applyChunks_RefetchChunks(t *testing.T) {
  383. // Discarding chunks via refetch_chunks should work the same for all results
  384. testcases := map[string]struct {
  385. result abci.ResponseApplySnapshotChunk_Result
  386. }{
  387. "accept": {abci.ResponseApplySnapshotChunk_ACCEPT},
  388. "abort": {abci.ResponseApplySnapshotChunk_ABORT},
  389. "retry": {abci.ResponseApplySnapshotChunk_RETRY},
  390. "retry_snapshot": {abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT},
  391. "reject_snapshot": {abci.ResponseApplySnapshotChunk_REJECT_SNAPSHOT},
  392. }
  393. for name, tc := range testcases {
  394. tc := tc
  395. t.Run(name, func(t *testing.T) {
  396. connQuery := &proxymocks.AppConnQuery{}
  397. connSnapshot := &proxymocks.AppConnSnapshot{}
  398. stateProvider := &mocks.StateProvider{}
  399. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  400. syncer := newSyncer(log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
  401. chunks, err := newChunkQueue(&snapshot{Height: 1, Format: 1, Chunks: 3}, "")
  402. require.NoError(t, err)
  403. added, err := chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: []byte{0}})
  404. require.True(t, added)
  405. require.NoError(t, err)
  406. added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 1, Chunk: []byte{1}})
  407. require.True(t, added)
  408. require.NoError(t, err)
  409. added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 2, Chunk: []byte{2}})
  410. require.True(t, added)
  411. require.NoError(t, err)
  412. // The first two chunks are accepted, before the last one asks for 1 to be refetched
  413. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  414. Index: 0, Chunk: []byte{0},
  415. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  416. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  417. Index: 1, Chunk: []byte{1},
  418. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  419. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  420. Index: 2, Chunk: []byte{2},
  421. }).Once().Return(&abci.ResponseApplySnapshotChunk{
  422. Result: tc.result,
  423. RefetchChunks: []uint32{1},
  424. }, nil)
  425. // Since removing the chunk will cause Next() to block, we spawn a goroutine, then
  426. // check the queue contents, and finally close the queue to end the goroutine.
  427. // We don't really care about the result of applyChunks, since it has separate test.
  428. go func() {
  429. syncer.applyChunks(chunks) //nolint:errcheck // purposefully ignore error
  430. }()
  431. time.Sleep(50 * time.Millisecond)
  432. assert.True(t, chunks.Has(0))
  433. assert.False(t, chunks.Has(1))
  434. assert.True(t, chunks.Has(2))
  435. err = chunks.Close()
  436. require.NoError(t, err)
  437. })
  438. }
  439. }
  440. func TestSyncer_applyChunks_RejectSenders(t *testing.T) {
  441. // Banning chunks senders via ban_chunk_senders should work the same for all results
  442. testcases := map[string]struct {
  443. result abci.ResponseApplySnapshotChunk_Result
  444. }{
  445. "accept": {abci.ResponseApplySnapshotChunk_ACCEPT},
  446. "abort": {abci.ResponseApplySnapshotChunk_ABORT},
  447. "retry": {abci.ResponseApplySnapshotChunk_RETRY},
  448. "retry_snapshot": {abci.ResponseApplySnapshotChunk_RETRY_SNAPSHOT},
  449. "reject_snapshot": {abci.ResponseApplySnapshotChunk_REJECT_SNAPSHOT},
  450. }
  451. for name, tc := range testcases {
  452. tc := tc
  453. t.Run(name, func(t *testing.T) {
  454. connQuery := &proxymocks.AppConnQuery{}
  455. connSnapshot := &proxymocks.AppConnSnapshot{}
  456. stateProvider := &mocks.StateProvider{}
  457. stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
  458. syncer := newSyncer(log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
  459. // Set up three peers across two snapshots, and ask for one of them to be banned.
  460. // It should be banned from all snapshots.
  461. peerA := simplePeer("a")
  462. peerB := simplePeer("b")
  463. peerC := simplePeer("c")
  464. s1 := &snapshot{Height: 1, Format: 1, Chunks: 3}
  465. s2 := &snapshot{Height: 2, Format: 1, Chunks: 3}
  466. _, err := syncer.AddSnapshot(peerA, s1)
  467. require.NoError(t, err)
  468. _, err = syncer.AddSnapshot(peerA, s2)
  469. require.NoError(t, err)
  470. _, err = syncer.AddSnapshot(peerB, s1)
  471. require.NoError(t, err)
  472. _, err = syncer.AddSnapshot(peerB, s2)
  473. require.NoError(t, err)
  474. _, err = syncer.AddSnapshot(peerC, s1)
  475. require.NoError(t, err)
  476. _, err = syncer.AddSnapshot(peerC, s2)
  477. require.NoError(t, err)
  478. chunks, err := newChunkQueue(s1, "")
  479. require.NoError(t, err)
  480. added, err := chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: []byte{0}, Sender: peerA.ID()})
  481. require.True(t, added)
  482. require.NoError(t, err)
  483. added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 1, Chunk: []byte{1}, Sender: peerB.ID()})
  484. require.True(t, added)
  485. require.NoError(t, err)
  486. added, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 2, Chunk: []byte{2}, Sender: peerC.ID()})
  487. require.True(t, added)
  488. require.NoError(t, err)
  489. // The first two chunks are accepted, before the last one asks for b sender to be rejected
  490. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  491. Index: 0, Chunk: []byte{0}, Sender: "a",
  492. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  493. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  494. Index: 1, Chunk: []byte{1}, Sender: "b",
  495. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  496. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  497. Index: 2, Chunk: []byte{2}, Sender: "c",
  498. }).Once().Return(&abci.ResponseApplySnapshotChunk{
  499. Result: tc.result,
  500. RejectSenders: []string{string(peerB.ID())},
  501. }, nil)
  502. // On retry, the last chunk will be tried again, so we just accept it then.
  503. if tc.result == abci.ResponseApplySnapshotChunk_RETRY {
  504. connSnapshot.On("ApplySnapshotChunkSync", ctx, abci.RequestApplySnapshotChunk{
  505. Index: 2, Chunk: []byte{2}, Sender: "c",
  506. }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
  507. }
  508. // We don't really care about the result of applyChunks, since it has separate test.
  509. // However, it will block on e.g. retry result, so we spawn a goroutine that will
  510. // be shut down when the chunk queue closes.
  511. go func() {
  512. syncer.applyChunks(chunks) //nolint:errcheck // purposefully ignore error
  513. }()
  514. time.Sleep(50 * time.Millisecond)
  515. s1peers := syncer.snapshots.GetPeers(s1)
  516. assert.Len(t, s1peers, 2)
  517. assert.EqualValues(t, "a", s1peers[0].ID())
  518. assert.EqualValues(t, "c", s1peers[1].ID())
  519. syncer.snapshots.GetPeers(s1)
  520. assert.Len(t, s1peers, 2)
  521. assert.EqualValues(t, "a", s1peers[0].ID())
  522. assert.EqualValues(t, "c", s1peers[1].ID())
  523. err = chunks.Close()
  524. require.NoError(t, err)
  525. })
  526. }
  527. }
  528. func TestSyncer_verifyApp(t *testing.T) {
  529. boom := errors.New("boom")
  530. s := &snapshot{Height: 3, Format: 1, Chunks: 5, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")}
  531. testcases := map[string]struct {
  532. response *abci.ResponseInfo
  533. err error
  534. expectErr error
  535. }{
  536. "verified": {&abci.ResponseInfo{
  537. LastBlockHeight: 3,
  538. LastBlockAppHash: []byte("app_hash"),
  539. AppVersion: 9,
  540. }, nil, nil},
  541. "invalid height": {&abci.ResponseInfo{
  542. LastBlockHeight: 5,
  543. LastBlockAppHash: []byte("app_hash"),
  544. AppVersion: 9,
  545. }, nil, errVerifyFailed},
  546. "invalid hash": {&abci.ResponseInfo{
  547. LastBlockHeight: 3,
  548. LastBlockAppHash: []byte("xxx"),
  549. AppVersion: 9,
  550. }, nil, errVerifyFailed},
  551. "error": {nil, boom, boom},
  552. }
  553. for name, tc := range testcases {
  554. tc := tc
  555. t.Run(name, func(t *testing.T) {
  556. connQuery := &proxymocks.AppConnQuery{}
  557. connSnapshot := &proxymocks.AppConnSnapshot{}
  558. stateProvider := &mocks.StateProvider{}
  559. syncer := newSyncer(log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")
  560. connQuery.On("InfoSync", ctx, proxy.RequestInfo).Return(tc.response, tc.err)
  561. version, err := syncer.verifyApp(s)
  562. unwrapped := errors.Unwrap(err)
  563. if unwrapped != nil {
  564. err = unwrapped
  565. }
  566. assert.Equal(t, tc.expectErr, err)
  567. if err == nil {
  568. assert.Equal(t, tc.response.AppVersion, version)
  569. }
  570. })
  571. }
  572. }
  573. func toABCI(s *snapshot) *abci.Snapshot {
  574. return &abci.Snapshot{
  575. Height: s.Height,
  576. Format: s.Format,
  577. Chunks: s.Chunks,
  578. Hash: s.Hash,
  579. Metadata: s.Metadata,
  580. }
  581. }