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.

400 lines
12 KiB

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