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.

383 lines
11 KiB

  1. package blockchain
  2. import (
  3. "bytes"
  4. "fmt"
  5. "runtime/debug"
  6. "strings"
  7. "testing"
  8. "time"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. "github.com/tendermint/tmlibs/db"
  12. "github.com/tendermint/tmlibs/log"
  13. "github.com/tendermint/tendermint/types"
  14. )
  15. func TestLoadBlockStoreStateJSON(t *testing.T) {
  16. db := db.NewMemDB()
  17. bsj := &BlockStoreStateJSON{Height: 1000}
  18. bsj.Save(db)
  19. retrBSJ := LoadBlockStoreStateJSON(db)
  20. assert.Equal(t, *bsj, retrBSJ, "expected the retrieved DBs to match")
  21. }
  22. func TestNewBlockStore(t *testing.T) {
  23. db := db.NewMemDB()
  24. db.Set(blockStoreKey, []byte(`{"height": "10000"}`))
  25. bs := NewBlockStore(db)
  26. require.Equal(t, int64(10000), bs.Height(), "failed to properly parse blockstore")
  27. panicCausers := []struct {
  28. data []byte
  29. wantErr string
  30. }{
  31. {[]byte("artful-doger"), "not unmarshal bytes"},
  32. {[]byte(" "), "unmarshal bytes"},
  33. }
  34. for i, tt := range panicCausers {
  35. // Expecting a panic here on trying to parse an invalid blockStore
  36. _, _, panicErr := doFn(func() (interface{}, error) {
  37. db.Set(blockStoreKey, tt.data)
  38. _ = NewBlockStore(db)
  39. return nil, nil
  40. })
  41. require.NotNil(t, panicErr, "#%d panicCauser: %q expected a panic", i, tt.data)
  42. assert.Contains(t, panicErr.Error(), tt.wantErr, "#%d data: %q", i, tt.data)
  43. }
  44. db.Set(blockStoreKey, nil)
  45. bs = NewBlockStore(db)
  46. assert.Equal(t, bs.Height(), int64(0), "expecting nil bytes to be unmarshaled alright")
  47. }
  48. func freshBlockStore() (*BlockStore, db.DB) {
  49. db := db.NewMemDB()
  50. return NewBlockStore(db), db
  51. }
  52. var (
  53. state, _ = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
  54. block = makeBlock(1, state)
  55. partSet = block.MakePartSet(2)
  56. part1 = partSet.GetPart(0)
  57. part2 = partSet.GetPart(1)
  58. seenCommit1 = &types.Commit{Precommits: []*types.Vote{{Height: 10,
  59. Timestamp: time.Now().UTC()}}}
  60. )
  61. // TODO: This test should be simplified ...
  62. func TestBlockStoreSaveLoadBlock(t *testing.T) {
  63. state, bs := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
  64. require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
  65. // check there are no blocks at various heights
  66. noBlockHeights := []int64{0, -1, 100, 1000, 2}
  67. for i, height := range noBlockHeights {
  68. if g := bs.LoadBlock(height); g != nil {
  69. t.Errorf("#%d: height(%d) got a block; want nil", i, height)
  70. }
  71. }
  72. // save a block
  73. block := makeBlock(bs.Height()+1, state)
  74. validPartSet := block.MakePartSet(2)
  75. seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10,
  76. Timestamp: time.Now().UTC()}}}
  77. bs.SaveBlock(block, partSet, seenCommit)
  78. require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
  79. incompletePartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 2})
  80. uncontiguousPartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 0})
  81. uncontiguousPartSet.AddPart(part2)
  82. header1 := types.Header{
  83. Height: 1,
  84. NumTxs: 100,
  85. ChainID: "block_test",
  86. Time: time.Now(),
  87. }
  88. header2 := header1
  89. header2.Height = 4
  90. // End of setup, test data
  91. commitAtH10 := &types.Commit{Precommits: []*types.Vote{{Height: 10,
  92. Timestamp: time.Now().UTC()}}}
  93. tuples := []struct {
  94. block *types.Block
  95. parts *types.PartSet
  96. seenCommit *types.Commit
  97. wantErr bool
  98. wantPanic string
  99. corruptBlockInDB bool
  100. corruptCommitInDB bool
  101. corruptSeenCommitInDB bool
  102. eraseCommitInDB bool
  103. eraseSeenCommitInDB bool
  104. }{
  105. {
  106. block: newBlock(&header1, commitAtH10),
  107. parts: validPartSet,
  108. seenCommit: seenCommit1,
  109. },
  110. {
  111. block: nil,
  112. wantPanic: "only save a non-nil block",
  113. },
  114. {
  115. block: newBlock(&header2, commitAtH10),
  116. parts: uncontiguousPartSet,
  117. wantPanic: "only save contiguous blocks", // and incomplete and uncontiguous parts
  118. },
  119. {
  120. block: newBlock(&header1, commitAtH10),
  121. parts: incompletePartSet,
  122. wantPanic: "only save complete block", // incomplete parts
  123. },
  124. {
  125. block: newBlock(&header1, commitAtH10),
  126. parts: validPartSet,
  127. seenCommit: seenCommit1,
  128. corruptCommitInDB: true, // Corrupt the DB's commit entry
  129. wantPanic: "Error reading block commit",
  130. },
  131. {
  132. block: newBlock(&header1, commitAtH10),
  133. parts: validPartSet,
  134. seenCommit: seenCommit1,
  135. wantPanic: "Error reading block",
  136. corruptBlockInDB: true, // Corrupt the DB's block entry
  137. },
  138. {
  139. block: newBlock(&header1, commitAtH10),
  140. parts: validPartSet,
  141. seenCommit: seenCommit1,
  142. // Expecting no error and we want a nil back
  143. eraseSeenCommitInDB: true,
  144. },
  145. {
  146. block: newBlock(&header1, commitAtH10),
  147. parts: validPartSet,
  148. seenCommit: seenCommit1,
  149. corruptSeenCommitInDB: true,
  150. wantPanic: "Error reading block seen commit",
  151. },
  152. {
  153. block: newBlock(&header1, commitAtH10),
  154. parts: validPartSet,
  155. seenCommit: seenCommit1,
  156. // Expecting no error and we want a nil back
  157. eraseCommitInDB: true,
  158. },
  159. }
  160. type quad struct {
  161. block *types.Block
  162. commit *types.Commit
  163. meta *types.BlockMeta
  164. seenCommit *types.Commit
  165. }
  166. for i, tuple := range tuples {
  167. bs, db := freshBlockStore()
  168. // SaveBlock
  169. res, err, panicErr := doFn(func() (interface{}, error) {
  170. bs.SaveBlock(tuple.block, tuple.parts, tuple.seenCommit)
  171. if tuple.block == nil {
  172. return nil, nil
  173. }
  174. if tuple.corruptBlockInDB {
  175. db.Set(calcBlockMetaKey(tuple.block.Height), []byte("block-bogus"))
  176. }
  177. bBlock := bs.LoadBlock(tuple.block.Height)
  178. bBlockMeta := bs.LoadBlockMeta(tuple.block.Height)
  179. if tuple.eraseSeenCommitInDB {
  180. db.Delete(calcSeenCommitKey(tuple.block.Height))
  181. }
  182. if tuple.corruptSeenCommitInDB {
  183. db.Set(calcSeenCommitKey(tuple.block.Height), []byte("bogus-seen-commit"))
  184. }
  185. bSeenCommit := bs.LoadSeenCommit(tuple.block.Height)
  186. commitHeight := tuple.block.Height - 1
  187. if tuple.eraseCommitInDB {
  188. db.Delete(calcBlockCommitKey(commitHeight))
  189. }
  190. if tuple.corruptCommitInDB {
  191. db.Set(calcBlockCommitKey(commitHeight), []byte("foo-bogus"))
  192. }
  193. bCommit := bs.LoadBlockCommit(commitHeight)
  194. return &quad{block: bBlock, seenCommit: bSeenCommit, commit: bCommit,
  195. meta: bBlockMeta}, nil
  196. })
  197. if subStr := tuple.wantPanic; subStr != "" {
  198. if panicErr == nil {
  199. t.Errorf("#%d: want a non-nil panic", i)
  200. } else if got := panicErr.Error(); !strings.Contains(got, subStr) {
  201. t.Errorf("#%d:\n\tgotErr: %q\nwant substring: %q", i, got, subStr)
  202. }
  203. continue
  204. }
  205. if tuple.wantErr {
  206. if err == nil {
  207. t.Errorf("#%d: got nil error", i)
  208. }
  209. continue
  210. }
  211. assert.Nil(t, panicErr, "#%d: unexpected panic", i)
  212. assert.Nil(t, err, "#%d: expecting a non-nil error", i)
  213. qua, ok := res.(*quad)
  214. if !ok || qua == nil {
  215. t.Errorf("#%d: got nil quad back; gotType=%T", i, res)
  216. continue
  217. }
  218. if tuple.eraseSeenCommitInDB {
  219. assert.Nil(t, qua.seenCommit,
  220. "erased the seenCommit in the DB hence we should get back a nil seenCommit")
  221. }
  222. if tuple.eraseCommitInDB {
  223. assert.Nil(t, qua.commit,
  224. "erased the commit in the DB hence we should get back a nil commit")
  225. }
  226. }
  227. }
  228. func TestLoadBlockPart(t *testing.T) {
  229. bs, db := freshBlockStore()
  230. height, index := int64(10), 1
  231. loadPart := func() (interface{}, error) {
  232. part := bs.LoadBlockPart(height, index)
  233. return part, nil
  234. }
  235. // Initially no contents.
  236. // 1. Requesting for a non-existent block shouldn't fail
  237. res, _, panicErr := doFn(loadPart)
  238. require.Nil(t, panicErr, "a non-existent block part shouldn't cause a panic")
  239. require.Nil(t, res, "a non-existent block part should return nil")
  240. // 2. Next save a corrupted block then try to load it
  241. db.Set(calcBlockPartKey(height, index), []byte("Tendermint"))
  242. res, _, panicErr = doFn(loadPart)
  243. require.NotNil(t, panicErr, "expecting a non-nil panic")
  244. require.Contains(t, panicErr.Error(), "Error reading block part")
  245. // 3. A good block serialized and saved to the DB should be retrievable
  246. db.Set(calcBlockPartKey(height, index), cdc.MustMarshalBinaryBare(part1))
  247. gotPart, _, panicErr := doFn(loadPart)
  248. require.Nil(t, panicErr, "an existent and proper block should not panic")
  249. require.Nil(t, res, "a properly saved block should return a proper block")
  250. require.Equal(t, gotPart.(*types.Part).Hash(), part1.Hash(),
  251. "expecting successful retrieval of previously saved block")
  252. }
  253. func TestLoadBlockMeta(t *testing.T) {
  254. bs, db := freshBlockStore()
  255. height := int64(10)
  256. loadMeta := func() (interface{}, error) {
  257. meta := bs.LoadBlockMeta(height)
  258. return meta, nil
  259. }
  260. // Initially no contents.
  261. // 1. Requesting for a non-existent blockMeta shouldn't fail
  262. res, _, panicErr := doFn(loadMeta)
  263. require.Nil(t, panicErr, "a non-existent blockMeta shouldn't cause a panic")
  264. require.Nil(t, res, "a non-existent blockMeta should return nil")
  265. // 2. Next save a corrupted blockMeta then try to load it
  266. db.Set(calcBlockMetaKey(height), []byte("Tendermint-Meta"))
  267. res, _, panicErr = doFn(loadMeta)
  268. require.NotNil(t, panicErr, "expecting a non-nil panic")
  269. require.Contains(t, panicErr.Error(), "Error reading block meta")
  270. // 3. A good blockMeta serialized and saved to the DB should be retrievable
  271. meta := &types.BlockMeta{}
  272. db.Set(calcBlockMetaKey(height), cdc.MustMarshalBinaryBare(meta))
  273. gotMeta, _, panicErr := doFn(loadMeta)
  274. require.Nil(t, panicErr, "an existent and proper block should not panic")
  275. require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ")
  276. require.Equal(t, cdc.MustMarshalBinaryBare(meta), cdc.MustMarshalBinaryBare(gotMeta),
  277. "expecting successful retrieval of previously saved blockMeta")
  278. }
  279. func TestBlockFetchAtHeight(t *testing.T) {
  280. state, bs := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
  281. require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
  282. block := makeBlock(bs.Height()+1, state)
  283. partSet := block.MakePartSet(2)
  284. seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10,
  285. Timestamp: time.Now().UTC()}}}
  286. bs.SaveBlock(block, partSet, seenCommit)
  287. require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
  288. blockAtHeight := bs.LoadBlock(bs.Height())
  289. bz1 := cdc.MustMarshalBinaryBare(block)
  290. bz2 := cdc.MustMarshalBinaryBare(blockAtHeight)
  291. require.Equal(t, bz1, bz2)
  292. require.Equal(t, block.Hash(), blockAtHeight.Hash(),
  293. "expecting a successful load of the last saved block")
  294. blockAtHeightPlus1 := bs.LoadBlock(bs.Height() + 1)
  295. require.Nil(t, blockAtHeightPlus1, "expecting an unsuccessful load of Height()+1")
  296. blockAtHeightPlus2 := bs.LoadBlock(bs.Height() + 2)
  297. require.Nil(t, blockAtHeightPlus2, "expecting an unsuccessful load of Height()+2")
  298. }
  299. func doFn(fn func() (interface{}, error)) (res interface{}, err error, panicErr error) {
  300. defer func() {
  301. if r := recover(); r != nil {
  302. switch e := r.(type) {
  303. case error:
  304. panicErr = e
  305. case string:
  306. panicErr = fmt.Errorf("%s", e)
  307. default:
  308. if st, ok := r.(fmt.Stringer); ok {
  309. panicErr = fmt.Errorf("%s", st)
  310. } else {
  311. panicErr = fmt.Errorf("%s", debug.Stack())
  312. }
  313. }
  314. }
  315. }()
  316. res, err = fn()
  317. return res, err, panicErr
  318. }
  319. func newBlock(hdr *types.Header, lastCommit *types.Commit) *types.Block {
  320. return &types.Block{
  321. Header: hdr,
  322. LastCommit: lastCommit,
  323. }
  324. }