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.

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