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.

411 lines
12 KiB

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