github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/blockdao/blockdao_test.go (about) 1 package blockdao 2 3 import ( 4 "context" 5 "hash/fnv" 6 "math/big" 7 "testing" 8 "time" 9 10 "github.com/pkg/errors" 11 "github.com/stretchr/testify/require" 12 13 "github.com/iotexproject/go-pkgs/hash" 14 15 "github.com/iotexproject/iotex-core/action" 16 "github.com/iotexproject/iotex-core/action/protocol" 17 "github.com/iotexproject/iotex-core/blockchain/block" 18 "github.com/iotexproject/iotex-core/blockchain/filedao" 19 "github.com/iotexproject/iotex-core/blockchain/genesis" 20 "github.com/iotexproject/iotex-core/db" 21 "github.com/iotexproject/iotex-core/pkg/compress" 22 "github.com/iotexproject/iotex-core/pkg/unit" 23 "github.com/iotexproject/iotex-core/test/identityset" 24 "github.com/iotexproject/iotex-core/testutil" 25 ) 26 27 func getTestBlocks(t *testing.T) []*block.Block { 28 amount := uint64(50 << 22) 29 tsf1, err := action.SignedTransfer(identityset.Address(28).String(), identityset.PrivateKey(28), 1, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0)) 30 require.NoError(t, err) 31 32 tsf2, err := action.SignedTransfer(identityset.Address(29).String(), identityset.PrivateKey(29), 2, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0)) 33 require.NoError(t, err) 34 35 tsf3, err := action.SignedTransfer(identityset.Address(30).String(), identityset.PrivateKey(30), 3, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0)) 36 require.NoError(t, err) 37 38 tsf4, err := action.SignedTransfer(identityset.Address(29).String(), identityset.PrivateKey(28), 2, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0)) 39 require.NoError(t, err) 40 41 tsf5, err := action.SignedTransfer(identityset.Address(30).String(), identityset.PrivateKey(29), 3, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0)) 42 require.NoError(t, err) 43 44 tsf6, err := action.SignedTransfer(identityset.Address(28).String(), identityset.PrivateKey(30), 4, big.NewInt(int64(amount)), nil, testutil.TestGasLimit, big.NewInt(0)) 45 require.NoError(t, err) 46 47 // create testing executions 48 execution1, err := action.SignedExecution(identityset.Address(31).String(), identityset.PrivateKey(28), 1, big.NewInt(1), 0, big.NewInt(0), nil) 49 require.NoError(t, err) 50 execution2, err := action.SignedExecution(identityset.Address(31).String(), identityset.PrivateKey(29), 2, big.NewInt(0), 0, big.NewInt(0), nil) 51 require.NoError(t, err) 52 execution3, err := action.SignedExecution(identityset.Address(31).String(), identityset.PrivateKey(30), 3, big.NewInt(2), 0, big.NewInt(0), nil) 53 require.NoError(t, err) 54 55 hash1 := hash.Hash256{} 56 fnv.New32().Sum(hash1[:]) 57 blk1, err := block.NewTestingBuilder(). 58 SetHeight(1). 59 SetPrevBlockHash(hash1). 60 SetTimeStamp(testutil.TimestampNow().UTC()). 61 AddActions(tsf1, tsf4, execution1). 62 SignAndBuild(identityset.PrivateKey(27)) 63 require.NoError(t, err) 64 65 hash2 := hash.Hash256{} 66 fnv.New32().Sum(hash2[:]) 67 blk2, err := block.NewTestingBuilder(). 68 SetHeight(2). 69 SetPrevBlockHash(hash2). 70 SetTimeStamp(testutil.TimestampNow().UTC()). 71 AddActions(tsf2, tsf5, execution2). 72 SignAndBuild(identityset.PrivateKey(27)) 73 require.NoError(t, err) 74 75 hash3 := hash.Hash256{} 76 fnv.New32().Sum(hash3[:]) 77 blk3, err := block.NewTestingBuilder(). 78 SetHeight(3). 79 SetPrevBlockHash(hash3). 80 SetTimeStamp(testutil.TimestampNow().UTC()). 81 AddActions(tsf3, tsf6, execution3). 82 SignAndBuild(identityset.PrivateKey(27)) 83 require.NoError(t, err) 84 return []*block.Block{&blk1, &blk2, &blk3} 85 } 86 87 // TODO: Move the test to filedao. The test is not for BlockDAO, but filedao. 88 // The current blockDAO's implementation is more about indexers and cache, which 89 // are not covered in the unit tests. 90 func TestBlockDAO(t *testing.T) { 91 require := require.New(t) 92 93 blks := getTestBlocks(t) 94 t1Hash, _ := blks[0].Actions[0].Hash() 95 t4Hash, _ := blks[0].Actions[1].Hash() 96 e1Hash, _ := blks[0].Actions[2].Hash() 97 t2Hash, _ := blks[1].Actions[0].Hash() 98 t5Hash, _ := blks[1].Actions[1].Hash() 99 e2Hash, _ := blks[1].Actions[2].Hash() 100 t3Hash, _ := blks[2].Actions[0].Hash() 101 t6Hash, _ := blks[2].Actions[1].Hash() 102 e3Hash, _ := blks[2].Actions[2].Hash() 103 104 addr28 := hash.BytesToHash160(identityset.Address(28).Bytes()) 105 addr29 := hash.BytesToHash160(identityset.Address(29).Bytes()) 106 addr30 := hash.BytesToHash160(identityset.Address(30).Bytes()) 107 addr31 := hash.BytesToHash160(identityset.Address(31).Bytes()) 108 109 type index struct { 110 addr hash.Hash160 111 hashes [][]byte 112 } 113 114 daoTests := []struct { 115 total uint64 116 hashTotal [][]byte 117 actions [4]index 118 }{ 119 { 120 9, 121 [][]byte{t1Hash[:], t4Hash[:], e1Hash[:], t2Hash[:], t5Hash[:], e2Hash[:], t3Hash[:], t6Hash[:], e3Hash[:]}, 122 [4]index{ 123 {addr28, [][]byte{t1Hash[:], t4Hash[:], e1Hash[:], t6Hash[:]}}, 124 {addr29, [][]byte{t4Hash[:], t2Hash[:], t5Hash[:], e2Hash[:]}}, 125 {addr30, [][]byte{t5Hash[:], t3Hash[:], t6Hash[:], e3Hash[:]}}, 126 {addr31, [][]byte{e1Hash[:], e2Hash[:], e3Hash[:]}}, 127 }, 128 }, 129 { 130 6, 131 [][]byte{t1Hash[:], t4Hash[:], e1Hash[:], t2Hash[:], t5Hash[:], e2Hash[:]}, 132 [4]index{ 133 {addr28, [][]byte{t1Hash[:], t4Hash[:], e1Hash[:]}}, 134 {addr29, [][]byte{t4Hash[:], t2Hash[:], t5Hash[:], e2Hash[:]}}, 135 {addr30, [][]byte{t5Hash[:]}}, 136 {addr31, [][]byte{e1Hash[:], e2Hash[:]}}, 137 }, 138 }, 139 { 140 3, 141 [][]byte{t1Hash[:], t4Hash[:], e1Hash[:]}, 142 [4]index{ 143 {addr28, [][]byte{t1Hash[:], t4Hash[:], e1Hash[:]}}, 144 {addr29, [][]byte{t4Hash[:]}}, 145 {addr30, nil}, 146 {addr31, [][]byte{e1Hash[:]}}, 147 }, 148 }, 149 { 150 0, 151 nil, 152 [4]index{ 153 {addr28, nil}, 154 {addr29, nil}, 155 {addr30, nil}, 156 {addr31, nil}, 157 }, 158 }, 159 } 160 161 // receipts for the 3 blocks 162 receipts := [][]*action.Receipt{ 163 { 164 {Status: 1, BlockHeight: 1, ActionHash: t1Hash, GasConsumed: 15, ContractAddress: "1"}, 165 {Status: 0, BlockHeight: 1, ActionHash: t4Hash, GasConsumed: 216, ContractAddress: "2"}, 166 {Status: 2, BlockHeight: 1, ActionHash: e1Hash, GasConsumed: 6, ContractAddress: "3"}, 167 }, 168 { 169 {Status: 3, BlockHeight: 2, ActionHash: t2Hash, GasConsumed: 1500, ContractAddress: "1"}, 170 {Status: 5, BlockHeight: 2, ActionHash: t5Hash, GasConsumed: 34, ContractAddress: "2"}, 171 {Status: 9, BlockHeight: 2, ActionHash: e2Hash, GasConsumed: 655, ContractAddress: "3"}, 172 }, 173 { 174 {Status: 7, BlockHeight: 3, ActionHash: t3Hash, GasConsumed: 488, ContractAddress: "1"}, 175 {Status: 6, BlockHeight: 3, ActionHash: t6Hash, GasConsumed: 2, ContractAddress: "2"}, 176 {Status: 2, BlockHeight: 3, ActionHash: e3Hash, GasConsumed: 1099, ContractAddress: "3"}, 177 }, 178 } 179 180 testBlockDao := func(dao BlockDAO, t *testing.T) { 181 ctx := protocol.WithBlockchainCtx( 182 genesis.WithGenesisContext(context.Background(), genesis.Default), 183 protocol.BlockchainCtx{ 184 ChainID: 1, 185 }) 186 require.NoError(dao.Start(ctx)) 187 defer func() { 188 require.NoError(dao.Stop(ctx)) 189 }() 190 require.True(dao.ContainsTransactionLog()) 191 192 for i := 0; i < 3; i++ { 193 // test putBlock/Receipt 194 blks[i].Receipts = receipts[i] 195 require.NoError(dao.PutBlock(ctx, blks[i])) 196 blks[i].Receipts = nil 197 tipBlk := blks[i] 198 199 // test FileDAO's API 200 hash, err := dao.GetBlockHash(tipBlk.Height()) 201 require.NoError(err) 202 require.Equal(tipBlk.HashBlock(), hash) 203 height, err := dao.GetBlockHeight(hash) 204 require.NoError(err) 205 require.Equal(tipBlk.Height(), height) 206 blk, err := dao.GetBlock(hash) 207 require.NoError(err) 208 require.Equal(len(blk.Actions), len(tipBlk.Actions)) 209 for i := 0; i < len(blk.Actions); i++ { 210 hashVal1, hashErr1 := blk.Actions[i].Hash() 211 require.NoError(hashErr1) 212 hashVal2, hashErr2 := tipBlk.Actions[i].Hash() 213 require.NoError(hashErr2) 214 require.Equal(hashVal1, hashVal2) 215 } 216 blk, err = dao.GetBlockByHeight(height) 217 require.NoError(err) 218 require.Equal(len(blk.Actions), len(tipBlk.Actions)) 219 for i := 0; i < len(blk.Actions); i++ { 220 hashVal1, hashErr1 := blk.Actions[i].Hash() 221 require.NoError(hashErr1) 222 hashVal2, hashErr2 := tipBlk.Actions[i].Hash() 223 require.NoError(hashErr2) 224 require.Equal(hashVal1, hashVal2) 225 } 226 r, err := dao.GetReceipts(height) 227 require.NoError(err) 228 require.Equal(len(receipts[i]), len(r)) 229 for j := range receipts[i] { 230 b1, err := r[j].Serialize() 231 require.NoError(err) 232 b2, err := receipts[i][j].Serialize() 233 require.NoError(err) 234 require.Equal(b1, b2) 235 } 236 237 // test BlockDAO's API, 2nd loop to test LRU cache 238 for i := 0; i < 2; i++ { 239 header, err := dao.Header(hash) 240 require.NoError(err) 241 require.Equal(&tipBlk.Header, header) 242 header, err = dao.HeaderByHeight(height) 243 require.NoError(err) 244 require.Equal(&tipBlk.Header, header) 245 footer, err := dao.FooterByHeight(height) 246 require.NoError(err) 247 require.Equal(&tipBlk.Footer, footer) 248 } 249 } 250 251 height, err := dao.Height() 252 require.NoError(err) 253 require.EqualValues(len(blks), height) 254 255 // commit an existing block 256 require.Equal(filedao.ErrAlreadyExist, dao.PutBlock(ctx, blks[2])) 257 258 // check non-exist block 259 h, err := dao.GetBlockHash(5) 260 require.Equal(db.ErrNotExist, errors.Cause(err)) 261 require.Equal(hash.ZeroHash256, h) 262 height, err = dao.GetBlockHeight(hash.ZeroHash256) 263 require.Equal(db.ErrNotExist, errors.Cause(err)) 264 require.EqualValues(0, height) 265 blk, err := dao.GetBlock(hash.ZeroHash256) 266 require.Equal(db.ErrNotExist, errors.Cause(err)) 267 require.Nil(blk) 268 blk, err = dao.GetBlockByHeight(5) 269 require.Equal(db.ErrNotExist, errors.Cause(err)) 270 require.Nil(blk) 271 r, err := dao.GetReceipts(5) 272 require.Equal(db.ErrNotExist, errors.Cause(err)) 273 require.Nil(r) 274 275 // Test GetReceipt/ActionByActionHash 276 for i, v := range daoTests[0].hashTotal { 277 blk := blks[i/3] 278 h := hash.BytesToHash256(v) 279 receipt, err := receiptByActionHash(receipts[i/3], h) 280 require.NoError(err) 281 b1, err := receipt.Serialize() 282 require.NoError(err) 283 b2, err := receipts[i/3][i%3].Serialize() 284 require.NoError(err) 285 require.Equal(b1, b2) 286 action, actIndex, err := blk.ActionByHash(h) 287 require.NoError(err) 288 require.Equal(int(actIndex), i%3) 289 require.Equal(blk.Actions[i%3], action) 290 } 291 } 292 293 testDeleteDao := func(dao filedao.FileDAO, t *testing.T) { 294 ctx := protocol.WithBlockchainCtx( 295 genesis.WithGenesisContext(context.Background(), genesis.Default), 296 protocol.BlockchainCtx{ 297 ChainID: 1, 298 }) 299 require.NoError(dao.Start(ctx)) 300 defer func() { 301 require.NoError(dao.Stop(ctx)) 302 }() 303 304 // put blocks 305 for i := 0; i < 3; i++ { 306 blks[i].Receipts = receipts[i] 307 require.NoError(dao.PutBlock(ctx, blks[i])) 308 blks[i].Receipts = nil 309 } 310 311 // delete tip block one by one, verify address/action after each deletion 312 for i, action := range daoTests { 313 if i == 0 { 314 // tests[0] is the whole address/action data at block height 3 315 continue 316 } 317 prevTipHeight, err := dao.Height() 318 require.NoError(err) 319 prevTipHash, err := dao.GetBlockHash(prevTipHeight) 320 require.NoError(err) 321 for { 322 height, err := dao.Height() 323 require.NoError(err) 324 if height <= prevTipHeight-1 { 325 break 326 } 327 require.NoError(dao.DeleteTipBlock()) 328 } 329 tipHeight, err := dao.Height() 330 require.NoError(err) 331 require.EqualValues(prevTipHeight-1, tipHeight) 332 _, err = dao.GetBlockHash(prevTipHeight) 333 require.Error(err) 334 _, err = dao.GetBlockHeight(prevTipHash) 335 require.Error(err) 336 337 if tipHeight == 0 { 338 h, err := dao.GetBlockHash(0) 339 require.NoError(err) 340 require.Equal(block.GenesisHash(), h) 341 continue 342 } 343 tipBlk := blks[tipHeight-1] 344 require.Equal(tipBlk.Height(), tipHeight) 345 346 // test FileDAO's API 347 h, err := dao.GetBlockHash(tipHeight) 348 require.NoError(err) 349 require.Equal(tipBlk.HashBlock(), h) 350 height, err := dao.GetBlockHeight(h) 351 require.NoError(err) 352 require.Equal(tipHeight, height) 353 blk, err := dao.GetBlock(h) 354 require.NoError(err) 355 require.Equal(len(blk.Actions), len(tipBlk.Actions)) 356 for i := 0; i < len(blk.Actions); i++ { 357 hashVal1, hashErr1 := blk.Actions[i].Hash() 358 require.NoError(hashErr1) 359 hashVal2, hashErr2 := tipBlk.Actions[i].Hash() 360 require.NoError(hashErr2) 361 require.Equal(hashVal1, hashVal2) 362 } 363 blk, err = dao.GetBlockByHeight(height) 364 require.NoError(err) 365 require.Equal(len(blk.Actions), len(tipBlk.Actions)) 366 for i := 0; i < len(blk.Actions); i++ { 367 hashVal1, hashErr1 := blk.Actions[i].Hash() 368 require.NoError(hashErr1) 369 hashVal2, hashErr2 := tipBlk.Actions[i].Hash() 370 require.NoError(hashErr2) 371 require.Equal(hashVal1, hashVal2) 372 } 373 374 // test BlockDAO's API, 2nd loop to test LRU cache 375 for i := 0; i < 2; i++ { 376 header, err := dao.Header(h) 377 require.NoError(err) 378 require.Equal(&tipBlk.Header, header) 379 header, err = dao.HeaderByHeight(height) 380 require.NoError(err) 381 require.Equal(&tipBlk.Header, header) 382 footer, err := dao.FooterByHeight(height) 383 require.NoError(err) 384 require.Equal(&tipBlk.Footer, footer) 385 } 386 387 // Test GetReceipt/ActionByActionHash 388 for i, v := range action.hashTotal { 389 blk := blks[i/3] 390 h := hash.BytesToHash256(v) 391 receipt, err := receiptByActionHash(receipts[i/3], h) 392 require.NoError(err) 393 b1, err := receipt.Serialize() 394 require.NoError(err) 395 b2, err := receipts[i/3][i%3].Serialize() 396 require.NoError(err) 397 require.Equal(b1, b2) 398 action, actIndex, err := blk.ActionByHash(h) 399 require.NoError(err) 400 require.Equal(int(actIndex), i%3) 401 require.Equal(blk.Actions[i%3], action) 402 } 403 } 404 } 405 406 testPath, err := testutil.PathOfTempFile("test-kv-store") 407 require.NoError(err) 408 defer func() { 409 testutil.CleanupPath(testPath) 410 }() 411 412 daoList := []struct { 413 inMemory, legacy bool 414 compressBlock string 415 }{ 416 {true, false, ""}, 417 {false, true, ""}, 418 {false, true, compress.Gzip}, 419 {false, false, ""}, 420 {false, false, compress.Gzip}, 421 {false, false, compress.Snappy}, 422 } 423 424 cfg := db.DefaultConfig 425 cfg.DbPath = testPath 426 genesis.SetGenesisTimestamp(genesis.Default.Timestamp) 427 block.LoadGenesisHash(&genesis.Default) 428 for _, v := range daoList { 429 testutil.CleanupPath(testPath) 430 dao, err := createFileDAO(v.inMemory, v.legacy, v.compressBlock, cfg) 431 require.NoError(err) 432 require.NotNil(dao) 433 t.Run("test store blocks", func(t *testing.T) { 434 testBlockDao(dao, t) 435 }) 436 } 437 438 for _, v := range daoList { 439 testutil.CleanupPath(testPath) 440 dao, err := createFileDAO(v.inMemory, v.legacy, v.compressBlock, cfg) 441 require.NoError(err) 442 require.NotNil(dao) 443 t.Run("test delete blocks", func(t *testing.T) { 444 testDeleteDao(dao, t) 445 }) 446 } 447 } 448 449 func createFileDAO(inMemory, legacy bool, compressBlock string, cfg db.Config) (filedao.FileDAO, error) { 450 if inMemory { 451 return filedao.NewFileDAOInMemForTest() 452 } 453 deser := block.NewDeserializer(4689) 454 if legacy { 455 return filedao.CreateFileDAO(true, cfg, deser) 456 } 457 458 cfg.Compressor = compressBlock 459 return filedao.NewFileDAO(cfg, deser) 460 } 461 462 func BenchmarkBlockCache(b *testing.B) { 463 test := func(cacheSize int, b *testing.B) { 464 b.StopTimer() 465 path := "test-kv-store" 466 testPath, err := testutil.PathOfTempFile(path) 467 require.NoError(b, err) 468 indexPath, err := testutil.PathOfTempFile(path) 469 require.NoError(b, err) 470 cfg := db.Config{ 471 NumRetries: 1, 472 } 473 defer func() { 474 testutil.CleanupPath(testPath) 475 testutil.CleanupPath(indexPath) 476 }() 477 cfg.DbPath = indexPath 478 cfg.DbPath = testPath 479 cfg.MaxCacheSize = cacheSize 480 deser := block.NewDeserializer(4689) 481 fileDAO, err := filedao.NewFileDAO(cfg, deser) 482 require.NoError(b, err) 483 blkDao := NewBlockDAOWithIndexersAndCache(fileDAO, []BlockIndexer{}, cfg.MaxCacheSize) 484 require.NoError(b, blkDao.Start(context.Background())) 485 defer func() { 486 require.NoError(b, blkDao.Stop(context.Background())) 487 }() 488 prevHash := hash.ZeroHash256 489 numBlks := 8640 490 for i := 1; i <= numBlks; i++ { 491 actions := make([]*action.SealedEnvelope, 10) 492 for j := 0; j < 10; j++ { 493 actions[j], err = action.SignedTransfer( 494 identityset.Address(j).String(), 495 identityset.PrivateKey(j+1), 496 1, 497 unit.ConvertIotxToRau(1), 498 nil, 499 testutil.TestGasLimit, 500 testutil.TestGasPrice, 501 ) 502 require.NoError(b, err) 503 } 504 tb := block.TestingBuilder{} 505 blk, err := tb.SetPrevBlockHash(prevHash). 506 SetVersion(1). 507 SetTimeStamp(time.Now()). 508 SetHeight(uint64(i)). 509 AddActions(actions...). 510 SignAndBuild(identityset.PrivateKey(0)) 511 require.NoError(b, err) 512 err = blkDao.PutBlock(context.Background(), &blk) 513 require.NoError(b, err) 514 prevHash = blk.HashBlock() 515 } 516 b.ResetTimer() 517 } 518 b.Run("cache", func(b *testing.B) { 519 test(8640, b) 520 }) 521 b.Run("no-cache", func(b *testing.B) { 522 test(0, b) 523 }) 524 } 525 526 func receiptByActionHash(receipts []*action.Receipt, h hash.Hash256) (*action.Receipt, error) { 527 for _, r := range receipts { 528 if r.ActionHash == h { 529 return r, nil 530 } 531 } 532 return nil, errors.Errorf("receipt of action %x isn't found", h) 533 }