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.

424 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. func TestBlockStoreSaveLoadBlock(t *testing.T) {
  91. state, bs := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
  92. require.Equal(t, bs.Height(), 0, "initially the height should be zero")
  93. noBlockHeights := []int{0, -1, 100, 1000, 2}
  94. for i, height := range noBlockHeights {
  95. if g := bs.LoadBlock(height); g != nil {
  96. t.Errorf("#%d: height(%d) got a block; want nil", i, height)
  97. }
  98. }
  99. block := makeBlock(bs.Height()+1, state)
  100. validPartSet := block.MakePartSet(2)
  101. seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10}}}
  102. bs.SaveBlock(block, partSet, seenCommit)
  103. require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
  104. incompletePartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 2})
  105. uncontiguousPartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 0})
  106. uncontiguousPartSet.AddPart(part2, false)
  107. header1 := types.Header{
  108. Height: 1,
  109. NumTxs: 100,
  110. ChainID: "block_test",
  111. }
  112. header2 := header1
  113. header2.Height = 4
  114. // End of setup, test data
  115. tuples := []struct {
  116. block *types.Block
  117. parts *types.PartSet
  118. seenCommit *types.Commit
  119. wantErr bool
  120. wantPanic string
  121. corruptBlockInDB bool
  122. corruptCommitInDB bool
  123. corruptSeenCommitInDB bool
  124. eraseCommitInDB bool
  125. eraseSeenCommitInDB bool
  126. }{
  127. {
  128. block: &types.Block{
  129. Header: &header1,
  130. LastCommit: &types.Commit{Precommits: []*types.Vote{{Height: 10}}},
  131. },
  132. parts: validPartSet,
  133. seenCommit: seenCommit1,
  134. },
  135. {
  136. block: nil,
  137. wantPanic: "only save a non-nil block",
  138. },
  139. {
  140. block: &types.Block{
  141. Header: &header2,
  142. LastCommit: &types.Commit{Precommits: []*types.Vote{{Height: 10}}},
  143. },
  144. parts: uncontiguousPartSet,
  145. wantPanic: "only save contiguous blocks", // and incomplete and uncontiguous parts
  146. },
  147. {
  148. block: &types.Block{
  149. Header: &header1,
  150. LastCommit: &types.Commit{Precommits: []*types.Vote{{Height: 10}}},
  151. },
  152. parts: incompletePartSet,
  153. wantPanic: "only save complete block", // incomplete parts
  154. },
  155. {
  156. block: &types.Block{
  157. Header: &header1,
  158. LastCommit: &types.Commit{Precommits: []*types.Vote{{Height: 10}}},
  159. },
  160. parts: validPartSet,
  161. seenCommit: seenCommit1,
  162. corruptCommitInDB: true, // Corrupt the DB's commit entry
  163. wantPanic: "rror reading commit",
  164. },
  165. {
  166. block: &types.Block{
  167. Header: &header1,
  168. LastCommit: &types.Commit{Precommits: []*types.Vote{{Height: 10}}},
  169. },
  170. parts: validPartSet,
  171. seenCommit: seenCommit1,
  172. wantPanic: "rror reading block",
  173. corruptBlockInDB: true, // Corrupt the DB's block entry
  174. },
  175. {
  176. block: &types.Block{
  177. Header: &header1,
  178. LastCommit: &types.Commit{Precommits: []*types.Vote{{Height: 10}}},
  179. },
  180. parts: validPartSet,
  181. seenCommit: seenCommit1,
  182. // Expecting no error and we want a nil back
  183. eraseSeenCommitInDB: true,
  184. },
  185. {
  186. block: &types.Block{
  187. Header: &header1,
  188. LastCommit: &types.Commit{Precommits: []*types.Vote{{Height: 10}}},
  189. },
  190. parts: validPartSet,
  191. seenCommit: seenCommit1,
  192. corruptSeenCommitInDB: true,
  193. wantPanic: "rror reading commit",
  194. },
  195. {
  196. block: &types.Block{
  197. Header: &header1,
  198. LastCommit: &types.Commit{Precommits: []*types.Vote{{Height: 10}}},
  199. },
  200. parts: validPartSet,
  201. seenCommit: seenCommit1,
  202. // Expecting no error and we want a nil back
  203. eraseCommitInDB: true,
  204. },
  205. }
  206. type quad struct {
  207. block *types.Block
  208. commit *types.Commit
  209. meta *types.BlockMeta
  210. seenCommit *types.Commit
  211. }
  212. for i, tuple := range tuples {
  213. bs, db := freshBlockStore()
  214. // SaveBlock
  215. res, err, panicErr := doFn(func() (interface{}, error) {
  216. bs.SaveBlock(tuple.block, tuple.parts, tuple.seenCommit)
  217. if tuple.block == nil {
  218. return nil, nil
  219. }
  220. if tuple.corruptBlockInDB {
  221. db.Set(calcBlockMetaKey(tuple.block.Height), []byte("block-bogus"))
  222. }
  223. bBlock := bs.LoadBlock(tuple.block.Height)
  224. bBlockMeta := bs.LoadBlockMeta(tuple.block.Height)
  225. if tuple.eraseSeenCommitInDB {
  226. db.Delete(calcSeenCommitKey(tuple.block.Height))
  227. }
  228. if tuple.corruptSeenCommitInDB {
  229. db.Set(calcSeenCommitKey(tuple.block.Height), []byte("bogus-seen-commit"))
  230. }
  231. bSeenCommit := bs.LoadSeenCommit(tuple.block.Height)
  232. commitHeight := tuple.block.Height - 1
  233. if tuple.eraseCommitInDB {
  234. db.Delete(calcBlockCommitKey(commitHeight))
  235. }
  236. if tuple.corruptCommitInDB {
  237. db.Set(calcBlockCommitKey(commitHeight), []byte("foo-bogus"))
  238. }
  239. bCommit := bs.LoadBlockCommit(commitHeight)
  240. return &quad{block: bBlock, seenCommit: bSeenCommit, commit: bCommit, meta: bBlockMeta}, nil
  241. })
  242. if subStr := tuple.wantPanic; subStr != "" {
  243. if panicErr == nil {
  244. t.Errorf("#%d: want a non-nil panic", i)
  245. } else if got := panicErr.Error(); !strings.Contains(got, subStr) {
  246. t.Errorf("#%d:\n\tgotErr: %q\nwant substring: %q", i, got, subStr)
  247. }
  248. continue
  249. }
  250. if tuple.wantErr {
  251. if err == nil {
  252. t.Errorf("#%d: got nil error", i)
  253. }
  254. continue
  255. }
  256. assert.Nil(t, panicErr, "#%d: unexpected panic", i)
  257. assert.Nil(t, err, "#%d: expecting a non-nil error", i)
  258. qua, ok := res.(*quad)
  259. if !ok || qua == nil {
  260. t.Errorf("#%d: got nil quad back; gotType=%T", i, res)
  261. continue
  262. }
  263. if tuple.eraseSeenCommitInDB {
  264. assert.Nil(t, qua.seenCommit, "erased the seenCommit in the DB hence we should get back a nil seenCommit")
  265. }
  266. if tuple.eraseCommitInDB {
  267. assert.Nil(t, qua.commit, "erased the commit in the DB hence we should get back a nil commit")
  268. }
  269. }
  270. }
  271. func binarySerializeIt(v interface{}) []byte {
  272. var n int
  273. var err error
  274. buf := new(bytes.Buffer)
  275. wire.WriteBinary(v, buf, &n, &err)
  276. return buf.Bytes()
  277. }
  278. func TestLoadBlockPart(t *testing.T) {
  279. bs, db := freshBlockStore()
  280. height, index := 10, 1
  281. loadPart := func() (interface{}, error) {
  282. part := bs.LoadBlockPart(height, index)
  283. return part, nil
  284. }
  285. // Initially no contents.
  286. // 1. Requesting for a non-existent block shouldn't fail
  287. res, _, panicErr := doFn(loadPart)
  288. require.Nil(t, panicErr, "a non-existent block part shouldn't cause a panic")
  289. require.Nil(t, res, "a non-existent block part should return nil")
  290. // 2. Next save a corrupted block then try to load it
  291. db.Set(calcBlockPartKey(height, index), []byte("Tendermint"))
  292. res, _, panicErr = doFn(loadPart)
  293. require.NotNil(t, panicErr, "expecting a non-nil panic")
  294. require.Contains(t, panicErr.Error(), "Error reading block part")
  295. // 3. A good block serialized and saved to the DB should be retrievable
  296. db.Set(calcBlockPartKey(height, index), binarySerializeIt(part1))
  297. gotPart, _, panicErr := doFn(loadPart)
  298. require.Nil(t, panicErr, "an existent and proper block should not panic")
  299. require.Nil(t, res, "a properly saved block should return a proper block")
  300. require.Equal(t, gotPart.(*types.Part).Hash(), part1.Hash(), "expecting successful retrieval of previously saved block")
  301. }
  302. func TestLoadBlockMeta(t *testing.T) {
  303. bs, db := freshBlockStore()
  304. height := 10
  305. loadMeta := func() (interface{}, error) {
  306. meta := bs.LoadBlockMeta(height)
  307. return meta, nil
  308. }
  309. // Initially no contents.
  310. // 1. Requesting for a non-existent blockMeta shouldn't fail
  311. res, _, panicErr := doFn(loadMeta)
  312. require.Nil(t, panicErr, "a non-existent blockMeta shouldn't cause a panic")
  313. require.Nil(t, res, "a non-existent blockMeta should return nil")
  314. // 2. Next save a corrupted blockMeta then try to load it
  315. db.Set(calcBlockMetaKey(height), []byte("Tendermint-Meta"))
  316. res, _, panicErr = doFn(loadMeta)
  317. require.NotNil(t, panicErr, "expecting a non-nil panic")
  318. require.Contains(t, panicErr.Error(), "Error reading block meta")
  319. // 3. A good blockMeta serialized and saved to the DB should be retrievable
  320. meta := &types.BlockMeta{}
  321. db.Set(calcBlockMetaKey(height), binarySerializeIt(meta))
  322. gotMeta, _, panicErr := doFn(loadMeta)
  323. require.Nil(t, panicErr, "an existent and proper block should not panic")
  324. require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ")
  325. require.Equal(t, binarySerializeIt(meta), binarySerializeIt(gotMeta), "expecting successful retrieval of previously saved blockMeta")
  326. }
  327. func TestBlockFetchAtHeight(t *testing.T) {
  328. state, bs := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
  329. require.Equal(t, bs.Height(), 0, "initially the height should be zero")
  330. block := makeBlock(bs.Height()+1, state)
  331. partSet := block.MakePartSet(2)
  332. seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10}}}
  333. bs.SaveBlock(block, partSet, seenCommit)
  334. require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
  335. blockAtHeight := bs.LoadBlock(bs.Height())
  336. require.Equal(t, block.Hash(), blockAtHeight.Hash(), "expecting a successful load of the last saved block")
  337. blockAtHeightPlus1 := bs.LoadBlock(bs.Height() + 1)
  338. require.Nil(t, blockAtHeightPlus1, "expecting an unsuccessful load of Height()+1")
  339. blockAtHeightPlus2 := bs.LoadBlock(bs.Height() + 2)
  340. require.Nil(t, blockAtHeightPlus2, "expecting an unsuccessful load of Height()+2")
  341. }
  342. func doFn(fn func() (interface{}, error)) (res interface{}, err error, panicErr error) {
  343. defer func() {
  344. if r := recover(); r != nil {
  345. switch e := r.(type) {
  346. case error:
  347. panicErr = e
  348. case string:
  349. panicErr = fmt.Errorf("%s", e)
  350. default:
  351. if st, ok := r.(fmt.Stringer); ok {
  352. panicErr = fmt.Errorf("%s", st)
  353. } else {
  354. panicErr = fmt.Errorf("%s", debug.Stack())
  355. }
  356. }
  357. }
  358. }()
  359. res, err = fn()
  360. return res, err, panicErr
  361. }