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.

421 lines
12 KiB

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