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.

672 lines
25 KiB

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