Repository: iotexproject/iotex-core
Pull request #4221 (2024-04-04 00:43:46)

[blockdao] Optimize derialization when retrieving receipts
# Description
`getTransactionReceipt` is monitored to consume lots of cpu resources when serving for web3 requests.


<img width="1252" alt="image" src="https://github.com/iotexproject/iotex-core/assets/55118568/672928bf-6e16-4f80-9ade-4287e9f72bae">

Block and Receipt are deserialized and cached separtedly to reduce duplicate computing


Fixes #(issue)

## Type of change
Please delete options that are not relevant.
- [] Bug fix (non-breaking change which fixes an issue)
- [] New feature (non-breaking change which adds functionality)
- [] Code refactor or improvement
- [] Breaking change (fix or feature that would cause a new or changed behavior of existing functionality)
- [] This change requires a documentation update

# How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
- [] make test
- [] fullsync
- [] Other test (please specify)

**Test Configuration**:
- Firmware version:
- Hardware:
- Toolchain:
- SDK:

# Checklist:
- [] My code follows the style guidelines of this project
- [] I have performed a self-review of my code
- [] I have commented my code, particularly in hard-to-understand areas
- [] I have made corresponding changes to the documentation
- [] My changes generate no new warnings
- [] I have added tests that prove my fix is effective or that my feature works
- [] New and existing unit tests pass locally with my changes
- [] Any dependent changes have been merged and published in downstream modules


sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4221) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [7.9% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4221&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4221)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) Report
Attention: Patch coverage is `67.50000%` with `26 lines` in your changes are missing coverage. Please review.
> Project coverage is 76.71%. Comparing base [(`e1f0636`)](https://app.codecov.io/gh/iotexproject/iotex-core/commit/e1f0636b27ca54fa57da8cadb3034431143833fa?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) to head [(`1bee8d5`)](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject).
> Report is 220 commits behind head on master.

| [Files](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) | Patch % | Lines |
|---|---|---|
| [blockchain/block/block\_deserializer.go](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?src=pr&el=tree&filepath=blockchain%2Fblock%2Fblock_deserializer.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject#diff-YmxvY2tjaGFpbi9ibG9jay9ibG9ja19kZXNlcmlhbGl6ZXIuZ28=) | 22.22% | [14 Missing :warning: ](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) |
| [blockchain/filedao/filedao\_v2\_util.go](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?src=pr&el=tree&filepath=blockchain%2Ffiledao%2Ffiledao_v2_util.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject#diff-YmxvY2tjaGFpbi9maWxlZGFvL2ZpbGVkYW9fdjJfdXRpbC5nbw==) | 76.47% | [5 Missing and 3 partials :warning: ](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) |
| [api/coreservice.go](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?src=pr&el=tree&filepath=api%2Fcoreservice.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject#diff-YXBpL2NvcmVzZXJ2aWNlLmdv) | 62.50% | [3 Missing :warning: ](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) |
| [api/web3server.go](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?src=pr&el=tree&filepath=api%2Fweb3server.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject#diff-YXBpL3dlYjNzZXJ2ZXIuZ28=) | 80.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4221      +/-   ##
==========================================
+ Coverage   75.38%   76.71%   +1.33%     
==========================================
  Files         303      340      +37     
  Lines       25923    29289    +3366     
==========================================
+ Hits        19541    22469    +2928     
- Misses       5360     5713     +353     
- Partials     1022     1107      +85     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/iotexproject/iotex-core/pull/4221?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iotexproject).

sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4221) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [7.5% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4221&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4221)


sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4221) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [7.5% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4221&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4221)



Patch:
diff --git a/api/coreservice.go b/api/coreservice.go
index 2c928a99b..565e73747 100644
--- a/api/coreservice.go
+++ b/api/coreservice.go
@@ -119,7 +119,7 @@ type (
 		// ActionsByAddress returns all actions associated with an address
 		ActionsByAddress(addr address.Address, start uint64, count uint64) ([]*iotexapi.ActionInfo, error)
 		// ActionByActionHash returns action by action hash
-		ActionByActionHash(h hash.Hash256) (*action.SealedEnvelope, hash.Hash256, uint64, uint32, error)
+		ActionByActionHash(h hash.Hash256) (*action.SealedEnvelope, *block.Block, uint32, error)
 		// PendingActionByActionHash returns action by action hash
 		PendingActionByActionHash(h hash.Hash256) (*action.SealedEnvelope, error)
 		// ActPoolActions returns the all Transaction Identifiers in the actpool
@@ -1089,24 +1089,24 @@ func (core *coreService) BlockHashByBlockHeight(blkHeight uint64) (hash.Hash256,
 }
 
 // ActionByActionHash returns action by action hash
-func (core *coreService) ActionByActionHash(h hash.Hash256) (*action.SealedEnvelope, hash.Hash256, uint64, uint32, error) {
+func (core *coreService) ActionByActionHash(h hash.Hash256) (*action.SealedEnvelope, *block.Block, uint32, error) {
 	if err := core.checkActionIndex(); err != nil {
-		return nil, hash.ZeroHash256, 0, 0, status.Error(codes.NotFound, blockindex.ErrActionIndexNA.Error())
+		return nil, nil, 0, status.Error(codes.NotFound, blockindex.ErrActionIndexNA.Error())
 	}
 
 	actIndex, err := core.indexer.GetActionIndex(h[:])
 	if err != nil {
-		return nil, hash.ZeroHash256, 0, 0, errors.Wrap(ErrNotFound, err.Error())
+		return nil, nil, 0, errors.Wrap(ErrNotFound, err.Error())
 	}
 	blk, err := core.dao.GetBlockByHeight(actIndex.BlockHeight())
 	if err != nil {
-		return nil, hash.ZeroHash256, 0, 0, errors.Wrap(ErrNotFound, err.Error())
+		return nil, nil, 0, errors.Wrap(ErrNotFound, err.Error())
 	}
 	selp, index, err := blk.ActionByHash(h)
 	if err != nil {
-		return nil, hash.ZeroHash256, 0, 0, errors.Wrap(ErrNotFound, err.Error())
+		return nil, nil, 0, errors.Wrap(ErrNotFound, err.Error())
 	}
-	return selp, blk.HashBlock(), actIndex.BlockHeight(), index, nil
+	return selp, blk, index, nil
 }
 
 // ActionByActionHash returns action by action hash
@@ -1290,9 +1290,9 @@ func (core *coreService) pendingAction(selp *action.SealedEnvelope) (*iotexapi.A
 }
 
 func (core *coreService) getAction(actHash hash.Hash256, checkPending bool) (*iotexapi.ActionInfo, error) {
-	selp, blkHash, blkHeight, actIndex, err := core.ActionByActionHash(actHash)
+	selp, blk, actIndex, err := core.ActionByActionHash(actHash)
 	if err == nil {
-		act, err := core.committedAction(selp, blkHash, blkHeight)
+		act, err := core.committedAction(selp, blk.HashBlock(), blk.Height())
 		if err != nil {
 			return nil, err
 		}
diff --git a/api/web3server.go b/api/web3server.go
index adce43041..67336acca 100644
--- a/api/web3server.go
+++ b/api/web3server.go
@@ -585,11 +585,11 @@ func (svr *web3Handler) getTransactionByHash(in *gjson.Result) (interface{}, err
 		return nil, err
 	}
 
-	selp, blkHash, _, _, err := svr.coreService.ActionByActionHash(actHash)
+	selp, blk, _, err := svr.coreService.ActionByActionHash(actHash)
 	if err == nil {
 		receipt, err := svr.coreService.ReceiptByActionHash(actHash)
 		if err == nil {
-			return svr.assembleConfirmedTransaction(blkHash, selp, receipt)
+			return svr.assembleConfirmedTransaction(blk.HashBlock(), selp, receipt)
 		}
 		if errors.Cause(err) == ErrNotFound {
 			return nil, nil
@@ -629,7 +629,7 @@ func (svr *web3Handler) getTransactionReceipt(in *gjson.Result) (interface{}, er
 	}
 
 	// acquire action receipt by action hash
-	selp, blockHash, _, _, err := svr.coreService.ActionByActionHash(actHash)
+	selp, blk, _, err := svr.coreService.ActionByActionHash(actHash)
 	if err != nil {
 		if errors.Cause(err) == ErrNotFound {
 			return nil, nil
@@ -649,19 +649,13 @@ func (svr *web3Handler) getTransactionReceipt(in *gjson.Result) (interface{}, er
 	}
 
 	// acquire logsBloom from blockMeta
-	blkHash := hex.EncodeToString(blockHash[:])
-	blk, err := svr.coreService.BlockByHash(blkHash)
-	if err != nil {
-		return nil, err
-	}
-
 	var logsBloomStr string
-	if logsBloom := blk.Block.LogsBloomfilter(); logsBloom != nil {
+	if logsBloom := blk.LogsBloomfilter(); logsBloom != nil {
 		logsBloomStr = hex.EncodeToString(logsBloom.Bytes())
 	}
 
 	return &getReceiptResult{
-		blockHash:       blockHash,
+		blockHash:       blk.HashBlock(),
 		from:            selp.SenderAddress(),
 		to:              to,
 		contractAddress: contractAddr,
diff --git a/blockchain/block/block_deserializer.go b/blockchain/block/block_deserializer.go
index 6c69e6a54..a59f096b7 100644
--- a/blockchain/block/block_deserializer.go
+++ b/blockchain/block/block_deserializer.go
@@ -101,25 +101,36 @@ func (bd *Deserializer) DeserializeBody(buf []byte) (*Body, error) {
 	return &b, nil
 }
 
-// FromBlockStoreProto converts protobuf to block store
-func (bd *Deserializer) FromBlockStoreProto(pb *iotextypes.BlockStore) (*Store, error) {
-	in := &Store{}
+func (bd *Deserializer) BlockFromBlockStoreProto(pb *iotextypes.BlockStore) (*Block, error) {
+	return bd.blockFromBlockStoreProto(pb)
+}
+
+func (bd *Deserializer) blockFromBlockStoreProto(pb *iotextypes.BlockStore) (*Block, error) {
 	blk, err := bd.FromBlockProto(pb.Block)
 	if err != nil {
 		return nil, err
 	}
-	// verify merkle root can match after deserialize
-	if err := blk.VerifyTxRoot(); err != nil {
-		return nil, err
-	}
+	// TODO: Reenable this if necessary
+	// // verify merkle root can match after deserialize
+	// if err := blk.VerifyTxRoot(); err != nil {
+	// 	return nil, err
+	// }
+	return blk, nil
+}
+
+func (bd *Deserializer) ReceiptsFromBlockStoreProto(pb *iotextypes.BlockStore) ([]*action.Receipt, error) {
+	return bd.receiptsFromBlockStoreProto(pb)
+}
 
-	in.Block = blk
+func (bd *Deserializer) receiptsFromBlockStoreProto(pb *iotextypes.BlockStore) ([]*action.Receipt, error) {
+	receipts := make([]*action.Receipt, 0)
 	for _, receiptPb := range pb.Receipts {
 		receipt := &action.Receipt{}
 		receipt.ConvertFromReceiptPb(receiptPb)
-		in.Receipts = append(in.Receipts, receipt)
+		receipts = append(receipts, receipt)
 	}
-	return in, nil
+
+	return receipts, nil
 }
 
 // DeserializeBlockStore de-serializes a block store
@@ -128,5 +139,16 @@ func (bd *Deserializer) DeserializeBlockStore(buf []byte) (*Store, error) {
 	if err := proto.Unmarshal(buf, &pb); err != nil {
 		return nil, errors.Wrap(err, "failed to unmarshal block store")
 	}
-	return bd.FromBlockStoreProto(&pb)
+	blk, err := bd.blockFromBlockStoreProto(&pb)
+	if err != nil {
+		return nil, err
+	}
+	receipts, err := bd.receiptsFromBlockStoreProto(&pb)
+	if err != nil {
+		return nil, err
+	}
+	return &Store{
+		Block:    blk,
+		Receipts: receipts,
+	}, nil
 }
diff --git a/blockchain/filedao/filedao_v2.go b/blockchain/filedao/filedao_v2.go
index e275a1275..91e630807 100644
--- a/blockchain/filedao/filedao_v2.go
+++ b/blockchain/filedao/filedao_v2.go
@@ -37,17 +37,19 @@ var (
 type (
 	// fileDAOv2 handles chain db file after file split activation at v1.1.2
 	fileDAOv2 struct {
-		filename  string
-		header    *FileHeader
-		tip       *FileTip
-		blkBuffer *stagingBuffer
-		blkCache  cache.LRUCache
-		kvStore   db.KVStore
-		batch     batch.KVStoreBatch
-		hashStore db.CountingIndex // store block hash
-		blkStore  db.CountingIndex // store raw blocks
-		sysStore  db.CountingIndex // store transaction log
-		deser     *block.Deserializer
+		filename        string
+		header          *FileHeader
+		tip             *FileTip
+		blkBuffer       *stagingBuffer
+		blkStorePbCache cache.LRUCache
+		blkCache        cache.LRUCache
+		receiptCache    cache.LRUCache
+		kvStore         db.KVStore
+		batch           batch.KVStoreBatch
+		hashStore       db.CountingIndex // store block hash
+		blkStore        db.CountingIndex // store raw blocks
+		sysStore        db.CountingIndex // store transaction log
+		deser           *block.Deserializer
 	}
 )
 
@@ -68,10 +70,12 @@ func newFileDAOv2(bottom uint64, cfg db.Config, deser *block.Deserializer) (*fil
 		tip: &FileTip{
 			Height: bottom - 1,
 		},
-		blkCache: cache.NewThreadSafeLruCache(16),
-		kvStore:  db.NewBoltDB(cfg),
-		batch:    batch.NewBatch(),
-		deser:    deser,
+		blkStorePbCache: cache.NewThreadSafeLruCache(16),
+		blkCache:        cache.NewThreadSafeLruCache(256),
+		receiptCache:    cache.NewThreadSafeLruCache(256),
+		kvStore:         db.NewBoltDB(cfg),
+		batch:           batch.NewBatch(),
+		deser:           deser,
 	}
 	return &fd, nil
 }
@@ -79,11 +83,13 @@ func newFileDAOv2(bottom uint64, cfg db.Config, deser *block.Deserializer) (*fil
 // openFileDAOv2 opens an existing v2 file
 func openFileDAOv2(cfg db.Config, deser *block.Deserializer) *fileDAOv2 {
 	return &fileDAOv2{
-		filename: cfg.DbPath,
-		blkCache: cache.NewThreadSafeLruCache(16),
-		kvStore:  db.NewBoltDB(cfg),
-		batch:    batch.NewBatch(),
-		deser:    deser,
+		filename:        cfg.DbPath,
+		blkStorePbCache: cache.NewThreadSafeLruCache(16),
+		blkCache:        cache.NewThreadSafeLruCache(256),
+		receiptCache:    cache.NewThreadSafeLruCache(256),
+		kvStore:         db.NewBoltDB(cfg),
+		batch:           batch.NewBatch(),
+		deser:           deser,
 	}
 }
 
@@ -185,19 +191,19 @@ func (fd *fileDAOv2) GetBlockByHeight(height uint64) (*block.Block, error) {
 	if height == 0 {
 		return block.GenesisBlock(), nil
 	}
-	blkInfo, err := fd.getBlockStore(height)
+	blk, err := fd.getBlock(height)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to get block at height %d", height)
 	}
-	return blkInfo.Block, nil
+	return blk, nil
 }
 
 func (fd *fileDAOv2) GetReceipts(height uint64) ([]*action.Receipt, error) {
-	blkInfo, err := fd.getBlockStore(height)
+	receipts, err := fd.getReceipt(height)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to get receipts at height %d", height)
 	}
-	return blkInfo.Receipts, nil
+	return receipts, nil
 }
 
 func (fd *fileDAOv2) ContainsTransactionLog() bool {
diff --git a/blockchain/filedao/filedao_v2_util.go b/blockchain/filedao/filedao_v2_util.go
index 6f6e0aa53..ea24e01fa 100644
--- a/blockchain/filedao/filedao_v2_util.go
+++ b/blockchain/filedao/filedao_v2_util.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/iotexproject/iotex-proto/golang/iotextypes"
 
+	"github.com/iotexproject/iotex-core/action"
 	"github.com/iotexproject/iotex-core/blockchain/block"
 	"github.com/iotexproject/iotex-core/db"
 	"github.com/iotexproject/iotex-core/db/batch"
@@ -172,23 +173,76 @@ func (fd *fileDAOv2) highestBlockOfStoreTip() uint64 {
 	return fd.header.Start + fd.blkStore.Size()*fd.header.BlockStoreSize - 1
 }
 
-func (fd *fileDAOv2) getBlockStore(height uint64) (*block.Store, error) {
+func (fd *fileDAOv2) getBlock(height uint64) (*block.Block, error) {
 	if !fd.ContainsHeight(height) {
 		return nil, db.ErrNotExist
 	}
+	// check whether block in staging buffer or not
+	storeKey := blockStoreKey(height, fd.header)
+	if storeKey >= fd.blkStore.Size() {
+		blkStore, err := fd.blkBuffer.Get(stagingKey(height, fd.header))
+		if err != nil {
+			return nil, err
+		}
+		return blkStore.Block, nil
+	}
+	// check whether block in read cache or not
+	if value, ok := fd.blkCache.Get(height); ok {
+		return value.(*block.Block), nil
+	}
+	// read from storage DB
+	blockStore, err := fd.getBlockStore(height)
+	if err != nil {
+		return nil, err
+	}
+	blk, err := fd.deser.BlockFromBlockStoreProto(blockStore)
+	if err != nil {
+		return nil, err
+	}
+	// add to read cache
+	fd.blkCache.Add(height, blk)
+	return blk, nil
+}
 
+func (fd *fileDAOv2) getReceipt(height uint64) ([]*action.Receipt, error) {
+	if !fd.ContainsHeight(height) {
+		return nil, db.ErrNotExist
+	}
 	// check whether block in staging buffer or not
 	storeKey := blockStoreKey(height, fd.header)
 	if storeKey >= fd.blkStore.Size() {
-		return fd.blkBuffer.Get(stagingKey(height, fd.header))
+		blkStore, err := fd.blkBuffer.Get(stagingKey(height, fd.header))
+		if err != nil {
+			return nil, err
+		}
+		return blkStore.Receipts, nil
+	}
+	// check whether receipts in read cache or not
+	if value, ok := fd.receiptCache.Get(height); ok {
+		return value.([]*action.Receipt), nil
+	}
+	// read from storage DB
+	blockStore, err := fd.getBlockStore(height)
+	if err != nil {
+		return nil, err
 	}
+	receipts, err := fd.deser.ReceiptsFromBlockStoreProto(blockStore)
+	if err != nil {
+		return nil, err
+	}
+	// add to read cache
+	fd.receiptCache.Add(height, receipts)
+	return receipts, nil
+}
 
-	// check whether block in read cache or not
-	if value, ok := fd.blkCache.Get(storeKey); ok {
+func (fd *fileDAOv2) getBlockStore(height uint64) (*iotextypes.BlockStore, error) {
+	// check whether blockStore in read cache or not
+	storeKey := blockStoreKey(height, fd.header)
+	if value, ok := fd.blkStorePbCache.Get(storeKey); ok {
 		pbInfos := value.(*iotextypes.BlockStores)
-		return fd.deser.FromBlockStoreProto(pbInfos.BlockStores[stagingKey(height, fd.header)])
+		return pbInfos.BlockStores[stagingKey(height, fd.header)], nil
 	}
-
+	// read from storage DB
 	value, err := fd.blkStore.Get(storeKey)
 	if err != nil {
 		return nil, err
@@ -204,8 +258,7 @@ func (fd *fileDAOv2) getBlockStore(height uint64) (*block.Store, error) {
 	if len(pbStores.BlockStores) != int(fd.header.BlockStoreSize) {
 		return nil, ErrDataCorruption
 	}
-
 	// add to read cache
-	fd.blkCache.Add(storeKey, pbStores)
-	return fd.deser.FromBlockStoreProto(pbStores.BlockStores[stagingKey(height, fd.header)])
+	fd.blkStorePbCache.Add(storeKey, pbStores)
+	return pbStores.BlockStores[stagingKey(height, fd.header)], nil
 }


Test patch:
diff --git a/api/grpcserver_integrity_test.go b/api/grpcserver_integrity_test.go
index 06092d1e9..955e6336d 100644
--- a/api/grpcserver_integrity_test.go
+++ b/api/grpcserver_integrity_test.go
@@ -2344,7 +2344,7 @@ func TestGrpcServer_GetActionByActionHashIntegrity(t *testing.T) {
 	}()
 
 	for _, test := range _getActionByActionHashTest {
-		ret, _, _, _, err := svr.core.ActionByActionHash(test.h)
+		ret, _, _, err := svr.core.ActionByActionHash(test.h)
 		require.NoError(err)
 		require.Equal(test.expectedNounce, ret.Envelope.Nonce())
 	}
diff --git a/api/web3server_test.go b/api/web3server_test.go
index 82fa9355c..354d8ee27 100644
--- a/api/web3server_test.go
+++ b/api/web3server_test.go
@@ -626,7 +626,15 @@ func TestGetTransactionByHash(t *testing.T) {
 		ContractAddress: "test",
 		TxIndex:         1,
 	}
-	core.EXPECT().ActionByActionHash(gomock.Any()).Return(selp, hash.Hash256b([]byte("test")), uint64(0), uint32(0), nil)
+	blk, err := block.NewTestingBuilder().
+		SetHeight(1).
+		SetVersion(111).
+		SetPrevBlockHash(hash.ZeroHash256).
+		SetTimeStamp(time.Now()).
+		AddActions(selp).
+		SignAndBuild(identityset.PrivateKey(0))
+	require.NoError(err)
+	core.EXPECT().ActionByActionHash(gomock.Any()).Return(selp, &blk, uint32(0), nil)
 	core.EXPECT().ReceiptByActionHash(gomock.Any()).Return(receipt, nil)
 	core.EXPECT().EVMNetworkID().Return(uint32(0))
 
@@ -642,7 +650,7 @@ func TestGetTransactionByHash(t *testing.T) {
 	require.Equal(receipt, rlt.receipt)
 
 	// get pending transaction
-	core.EXPECT().ActionByActionHash(gomock.Any()).Return(nil, hash.ZeroHash256, uint64(0), uint32(0), ErrNotFound)
+	core.EXPECT().ActionByActionHash(gomock.Any()).Return(nil, nil, uint32(0), ErrNotFound)
 	core.EXPECT().PendingActionByActionHash(gomock.Any()).Return(selp, nil)
 	core.EXPECT().EVMNetworkID().Return(uint32(0))
 	ret, err = web3svr.getTransactionByHash(&in)
@@ -657,7 +665,7 @@ func TestGetTransactionByHash(t *testing.T) {
 	require.NoError(err)
 	txHash, err = selp.Hash()
 	require.NoError(err)
-	core.EXPECT().ActionByActionHash(gomock.Any()).Return(nil, hash.ZeroHash256, uint64(0), uint32(0), ErrNotFound)
+	core.EXPECT().ActionByActionHash(gomock.Any()).Return(nil, nil, uint32(0), ErrNotFound)
 	core.EXPECT().PendingActionByActionHash(gomock.Any()).Return(selp, nil)
 	core.EXPECT().EVMNetworkID().Return(uint32(0))
 	ret, err = web3svr.getTransactionByHash(&in)
@@ -737,8 +745,6 @@ func TestGetTransactionReceipt(t *testing.T) {
 		ContractAddress: "test",
 		TxIndex:         1,
 	}
-	core.EXPECT().ActionByActionHash(gomock.Any()).Return(selp, hash.Hash256b([]byte("test")), uint64(0), uint32(0), nil)
-	core.EXPECT().ReceiptByActionHash(gomock.Any()).Return(receipt, nil)
 	blk, err := block.NewTestingBuilder().
 		SetHeight(1).
 		SetVersion(111).
@@ -747,9 +753,8 @@ func TestGetTransactionReceipt(t *testing.T) {
 		AddActions(selp).
 		SignAndBuild(identityset.PrivateKey(0))
 	require.NoError(err)
-	core.EXPECT().BlockByHash(gomock.Any()).Return(&apitypes.BlockWithReceipts{
-		Block: &blk,
-	}, nil)
+	core.EXPECT().ActionByActionHash(gomock.Any()).Return(selp, &blk, uint32(0), nil)
+	core.EXPECT().ReceiptByActionHash(gomock.Any()).Return(receipt, nil)
 
 	t.Run("nil params", func(t *testing.T) {
 		inNil := gjson.Parse(`{"params":[]}`)
diff --git a/blockchain/block/block_deserializer_test.go b/blockchain/block/block_deserializer_test.go
index b0f4f2fa5..0b9b1b1b3 100644
--- a/blockchain/block/block_deserializer_test.go
+++ b/blockchain/block/block_deserializer_test.go
@@ -45,10 +45,10 @@ func TestBlockStoreDeserializer(t *testing.T) {
 	require.NotNil(storeProto)
 
 	bd := Deserializer{}
-	store1, err := bd.FromBlockStoreProto(storeProto)
+	store1, err := bd.BlockFromBlockStoreProto(storeProto)
 	require.NoError(err)
 
-	require.Equal(store1.Block.height, store.Block.height)
-	require.Equal(store1.Block.Header.prevBlockHash, store.Block.Header.prevBlockHash)
-	require.Equal(store1.Block.Header.blockSig, store.Block.Header.blockSig)
+	require.Equal(store1.height, store.Block.height)
+	require.Equal(store1.Header.prevBlockHash, store.Block.Header.prevBlockHash)
+	require.Equal(store1.Header.blockSig, store.Block.Header.blockSig)
 }
diff --git a/blockchain/filedao/testing.go b/blockchain/filedao/testing.go
index 6afc3805f..925cb7b57 100644
--- a/blockchain/filedao/testing.go
+++ b/blockchain/filedao/testing.go
@@ -66,10 +66,12 @@ func newFileDAOv2InMem(bottom uint64) (*fileDAOv2, error) {
 		tip: &FileTip{
 			Height: bottom - 1,
 		},
-		blkCache: cache.NewThreadSafeLruCache(16),
-		kvStore:  db.NewMemKVStore(),
-		batch:    batch.NewBatch(),
-		deser:    block.NewDeserializer(_defaultEVMNetworkID),
+		blkStorePbCache: cache.NewThreadSafeLruCache(16),
+		blkCache:        cache.NewThreadSafeLruCache(256),
+		receiptCache:    cache.NewThreadSafeLruCache(256),
+		kvStore:         db.NewMemKVStore(),
+		batch:           batch.NewBatch(),
+		deser:           block.NewDeserializer(_defaultEVMNetworkID),
 	}
 	return &fd, nil
 }
diff --git a/test/mock/mock_apicoreservice/mock_apicoreservice.go b/test/mock/mock_apicoreservice/mock_apicoreservice.go
index 0528dd1ec..4ebb8bd25 100644
--- a/test/mock/mock_apicoreservice/mock_apicoreservice.go
+++ b/test/mock/mock_apicoreservice/mock_apicoreservice.go
@@ -78,15 +78,14 @@ func (mr *MockCoreServiceMockRecorder) Action(actionHash, checkPending interface
 }
 
 // ActionByActionHash mocks base method.
-func (m *MockCoreService) ActionByActionHash(h hash.Hash256) (*action.SealedEnvelope, hash.Hash256, uint64, uint32, error) {
+func (m *MockCoreService) ActionByActionHash(h hash.Hash256) (*action.SealedEnvelope, *block.Block, uint32, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "ActionByActionHash", h)
 	ret0, _ := ret[0].(*action.SealedEnvelope)
-	ret1, _ := ret[1].(hash.Hash256)
-	ret2, _ := ret[2].(uint64)
-	ret3, _ := ret[3].(uint32)
-	ret4, _ := ret[4].(error)
-	return ret0, ret1, ret2, ret3, ret4
+	ret1, _ := ret[1].(*block.Block)
+	ret2, _ := ret[2].(uint32)
+	ret3, _ := ret[3].(error)
+	return ret0, ret1, ret2, ret3
 }
 
 // ActionByActionHash indicates an expected call of ActionByActionHash.