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.

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