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.

336 lines
9.4 KiB

  1. package v2
  2. import (
  3. "testing"
  4. "github.com/stretchr/testify/assert"
  5. "github.com/tendermint/tendermint/p2p"
  6. tdState "github.com/tendermint/tendermint/state"
  7. "github.com/tendermint/tendermint/types"
  8. )
  9. // pcBlock is a test helper structure with simple types. Its purpose is to help with test readability.
  10. type pcBlock struct {
  11. pid string
  12. height int64
  13. }
  14. // params is a test structure used to create processor state.
  15. type params struct {
  16. height int64
  17. items []pcBlock
  18. blocksSynced int64
  19. verBL []int64
  20. appBL []int64
  21. draining bool
  22. }
  23. // makePcBlock makes an empty block.
  24. func makePcBlock(height int64) *types.Block {
  25. return &types.Block{Header: types.Header{Height: height}}
  26. }
  27. // makeState takes test parameters and creates a specific processor state.
  28. func makeState(p *params) *pcState {
  29. var (
  30. tdState = tdState.State{}
  31. context = newMockProcessorContext(p.verBL, p.appBL)
  32. )
  33. state := newPcState(p.height, tdState, "test", context)
  34. for _, item := range p.items {
  35. _ = state.enqueue(p2p.ID(item.pid), makePcBlock(item.height), item.height)
  36. }
  37. state.blocksSynced = p.blocksSynced
  38. state.draining = p.draining
  39. return state
  40. }
  41. func mBlockResponse(peerID p2p.ID, height int64) *bcBlockResponse {
  42. return &bcBlockResponse{
  43. peerID: peerID,
  44. block: makePcBlock(height),
  45. height: height,
  46. }
  47. }
  48. type pcFsmMakeStateValues struct {
  49. currentState *params
  50. event Event
  51. wantState *params
  52. wantNextEvent Event
  53. wantErr error
  54. wantPanic bool
  55. }
  56. type testFields struct {
  57. name string
  58. steps []pcFsmMakeStateValues
  59. }
  60. func executeProcessorTests(t *testing.T, tests []testFields) {
  61. for _, tt := range tests {
  62. tt := tt
  63. t.Run(tt.name, func(t *testing.T) {
  64. var state *pcState
  65. for _, step := range tt.steps {
  66. defer func() {
  67. r := recover()
  68. if (r != nil) != step.wantPanic {
  69. t.Errorf("recover = %v, wantPanic = %v", r, step.wantPanic)
  70. }
  71. }()
  72. // First step must always initialise the currentState as state.
  73. if step.currentState != nil {
  74. state = makeState(step.currentState)
  75. }
  76. if state == nil {
  77. panic("Bad (initial?) step")
  78. }
  79. nextEvent, err := state.handle(step.event)
  80. t.Log(state)
  81. assert.Equal(t, step.wantErr, err)
  82. assert.Equal(t, makeState(step.wantState), state)
  83. assert.Equal(t, step.wantNextEvent, nextEvent)
  84. // Next step may use the wantedState as their currentState.
  85. state = makeState(step.wantState)
  86. }
  87. })
  88. }
  89. }
  90. func TestPcBlockResponse(t *testing.T) {
  91. tests := []testFields{
  92. {
  93. name: "add one block",
  94. steps: []pcFsmMakeStateValues{
  95. {
  96. currentState: &params{}, event: mBlockResponse("P1", 1),
  97. wantState: &params{items: []pcBlock{{"P1", 1}}}, wantNextEvent: noOp,
  98. },
  99. },
  100. },
  101. {
  102. name: "add two blocks",
  103. steps: []pcFsmMakeStateValues{
  104. {
  105. currentState: &params{}, event: mBlockResponse("P1", 3),
  106. wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp,
  107. },
  108. { // use previous wantState as currentState,
  109. event: mBlockResponse("P1", 4),
  110. wantState: &params{items: []pcBlock{{"P1", 3}, {"P1", 4}}}, wantNextEvent: noOp,
  111. },
  112. },
  113. },
  114. {
  115. name: "add duplicate block from same peer",
  116. steps: []pcFsmMakeStateValues{
  117. {
  118. currentState: &params{}, event: mBlockResponse("P1", 3),
  119. wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp,
  120. },
  121. { // use previous wantState as currentState,
  122. event: mBlockResponse("P1", 3),
  123. wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcDuplicateBlock{},
  124. },
  125. },
  126. },
  127. {
  128. name: "add duplicate block from different peer",
  129. steps: []pcFsmMakeStateValues{
  130. {
  131. currentState: &params{}, event: mBlockResponse("P1", 3),
  132. wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp,
  133. },
  134. { // use previous wantState as currentState,
  135. event: mBlockResponse("P2", 3),
  136. wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcDuplicateBlock{},
  137. },
  138. },
  139. },
  140. {
  141. name: "attempt to add block with height equal to state.height",
  142. steps: []pcFsmMakeStateValues{
  143. {
  144. currentState: &params{height: 2, items: []pcBlock{{"P1", 3}}}, event: mBlockResponse("P1", 2),
  145. wantState: &params{height: 2, items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcShortBlock{},
  146. },
  147. },
  148. },
  149. {
  150. name: "attempt to add block with height smaller than state.height",
  151. steps: []pcFsmMakeStateValues{
  152. {
  153. currentState: &params{height: 2, items: []pcBlock{{"P1", 3}}}, event: mBlockResponse("P1", 1),
  154. wantState: &params{height: 2, items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcShortBlock{},
  155. },
  156. },
  157. },
  158. }
  159. executeProcessorTests(t, tests)
  160. }
  161. func TestPcProcessBlockSuccess(t *testing.T) {
  162. tests := []testFields{
  163. {
  164. name: "noop - no blocks over current height",
  165. steps: []pcFsmMakeStateValues{
  166. {
  167. currentState: &params{}, event: pcProcessBlock{},
  168. wantState: &params{}, wantNextEvent: noOp,
  169. },
  170. },
  171. },
  172. {
  173. name: "noop - high new blocks",
  174. steps: []pcFsmMakeStateValues{
  175. {
  176. currentState: &params{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, event: pcProcessBlock{},
  177. wantState: &params{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, wantNextEvent: noOp,
  178. },
  179. },
  180. },
  181. {
  182. name: "blocks H+1 and H+2 present",
  183. steps: []pcFsmMakeStateValues{
  184. {
  185. currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: pcProcessBlock{},
  186. wantState: &params{height: 1, items: []pcBlock{{"P2", 2}}, blocksSynced: 1},
  187. wantNextEvent: pcBlockProcessed{height: 1, peerID: "P1"},
  188. },
  189. },
  190. },
  191. {
  192. name: "blocks H+1 and H+2 present after draining",
  193. steps: []pcFsmMakeStateValues{
  194. { // some contiguous blocks - on stop check draining is set
  195. currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}}, event: pcStop{},
  196. wantState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}, draining: true},
  197. wantNextEvent: noOp,
  198. },
  199. {
  200. event: pcProcessBlock{},
  201. wantState: &params{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true},
  202. wantNextEvent: pcBlockProcessed{height: 1, peerID: "P1"},
  203. },
  204. { // finish when H+1 or/and H+2 are missing
  205. event: pcProcessBlock{},
  206. wantState: &params{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true},
  207. wantNextEvent: noOp,
  208. wantErr: pcFinished{height: 1},
  209. },
  210. },
  211. },
  212. }
  213. executeProcessorTests(t, tests)
  214. }
  215. func TestPcProcessBlockFailures(t *testing.T) {
  216. tests := []testFields{
  217. {
  218. name: "blocks H+1 and H+2 present - H+1 verification fails ",
  219. steps: []pcFsmMakeStateValues{
  220. {
  221. currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, event: pcProcessBlock{},
  222. wantState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}},
  223. wantNextEvent: pcBlockVerificationFailure{peerID: "P1", height: 1},
  224. },
  225. },
  226. },
  227. {
  228. name: "blocks H+1 and H+2 present - H+1 applyBlock fails ",
  229. steps: []pcFsmMakeStateValues{
  230. {
  231. currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, event: pcProcessBlock{},
  232. wantState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, wantPanic: true,
  233. },
  234. },
  235. },
  236. }
  237. executeProcessorTests(t, tests)
  238. }
  239. func TestPcPeerError(t *testing.T) {
  240. tests := []testFields{
  241. {
  242. name: "peer not present",
  243. steps: []pcFsmMakeStateValues{
  244. {
  245. currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: &peerError{peerID: "P3"},
  246. wantState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
  247. wantNextEvent: noOp,
  248. },
  249. },
  250. },
  251. {
  252. name: "some blocks are from errored peer",
  253. steps: []pcFsmMakeStateValues{
  254. {
  255. currentState: &params{items: []pcBlock{{"P1", 100}, {"P1", 99}, {"P2", 101}}}, event: &peerError{peerID: "P1"},
  256. wantState: &params{items: []pcBlock{{"P2", 101}}},
  257. wantNextEvent: noOp,
  258. },
  259. },
  260. },
  261. {
  262. name: "all blocks are from errored peer",
  263. steps: []pcFsmMakeStateValues{
  264. {
  265. currentState: &params{items: []pcBlock{{"P1", 100}, {"P1", 99}}}, event: &peerError{peerID: "P1"},
  266. wantState: &params{},
  267. wantNextEvent: noOp,
  268. },
  269. },
  270. },
  271. }
  272. executeProcessorTests(t, tests)
  273. }
  274. func TestStop(t *testing.T) {
  275. tests := []testFields{
  276. {
  277. name: "no blocks",
  278. steps: []pcFsmMakeStateValues{
  279. {
  280. currentState: &params{height: 100, items: []pcBlock{}, blocksSynced: 100}, event: pcStop{},
  281. wantState: &params{height: 100, items: []pcBlock{}, blocksSynced: 100},
  282. wantNextEvent: noOp,
  283. wantErr: pcFinished{height: 100, blocksSynced: 100},
  284. },
  285. },
  286. },
  287. {
  288. name: "maxHeight+1 block present",
  289. steps: []pcFsmMakeStateValues{
  290. {
  291. currentState: &params{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100}, event: pcStop{},
  292. wantState: &params{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100},
  293. wantNextEvent: noOp,
  294. wantErr: pcFinished{height: 100, blocksSynced: 100},
  295. },
  296. },
  297. },
  298. {
  299. name: "more blocks present",
  300. steps: []pcFsmMakeStateValues{
  301. {
  302. currentState: &params{height: 100, items: []pcBlock{{"P1", 101}, {"P1", 102}}, blocksSynced: 100}, event: pcStop{},
  303. wantState: &params{height: 100, items: []pcBlock{{"P1", 101}, {"P1", 102}}, blocksSynced: 100, draining: true},
  304. wantNextEvent: noOp,
  305. wantErr: nil,
  306. },
  307. },
  308. },
  309. }
  310. executeProcessorTests(t, tests)
  311. }