Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ type BlockChain struct {
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
receiptsCache *lru.Cache[common.Hash, []*types.Receipt] // Receipts cache with all fields derived
blockCache *lru.Cache[common.Hash, *types.Block]
witnessCache *lru.Cache[common.Hash, []byte] // Witness cache for RLP-encoded witnesses

txLookupLock sync.RWMutex
txLookupCache *lru.Cache[common.Hash, txLookup]
Expand Down Expand Up @@ -441,6 +442,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks),
witnessCache: lru.NewCache[common.Hash, []byte](bodyCacheLimit),
engine: engine,
borReceiptsCache: lru.NewCache[common.Hash, *types.Receipt](receiptsCacheLimit),
borReceiptsRLPCache: lru.NewCache[common.Hash, rlp.RawValue](receiptsCacheLimit),
Expand Down Expand Up @@ -1402,6 +1404,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
bc.receiptsCache.Purge()
bc.blockCache.Purge()
bc.txLookupCache.Purge()
bc.witnessCache.Purge()
bc.borReceiptsCache.Purge()

// Clear safe block, finalized block if needed
Expand Down Expand Up @@ -2238,7 +2241,10 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.

log.Debug("Writing witness", "block", block.NumberU64(), "hash", block.Hash(), "header", statedb.Witness().Header())

rawdb.WriteWitness(blockBatch, block.Hash(), witBuf.Bytes())
witnessBytes := witBuf.Bytes()
rawdb.WriteWitness(blockBatch, block.Hash(), witnessBytes)
// Cache the witness for faster retrieval during block import
bc.witnessCache.Add(block.Hash(), witnessBytes)
Comment thread
pratikspatil024 marked this conversation as resolved.
Outdated
} else {
log.Debug("No witness to write", "block", block.NumberU64())
}
Expand Down
17 changes: 17 additions & 0 deletions core/blockchain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,23 @@ func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {
return body
}

// GetWitness retrieves a witness in RLP encoding from the database by hash,
// caching it if found.
func (bc *BlockChain) GetWitness(hash common.Hash) []byte {
// Short circuit if the witness is already in the cache, retrieve otherwise
if cached, ok := bc.witnessCache.Get(hash); ok {
return cached
}

witness := rawdb.ReadWitness(bc.db, hash)
if len(witness) == 0 {
return nil
}
// Cache the found witness for next time and return
bc.witnessCache.Add(hash, witness)
return witness
}

// HasWitness checks if a witness is present in the database or not.
func (bc *BlockChain) HasWitness(hash common.Hash) bool {
return rawdb.HasWitness(bc.db, hash)
Expand Down
95 changes: 95 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5892,3 +5892,98 @@ func TestSplitReceiptsAndDeriveFields(t *testing.T) {
require.Equal(t, rlp.RawValue(stateSyncEncoded), stateSync, fmt.Sprintf("case: %s, state-sync receipts mismatch, got: %v, expected: %v", test.name, stateSync, stateSyncEncoded))
}
}

// TestWitnessCache tests the witness caching functionality to ensure witnesses
// are properly cached during writes and retrieved from cache during reads.
func TestWitnessCache(t *testing.T) {
// Setup: Create a test blockchain
engine := ethash.NewFaker()
gspec := &Genesis{
Config: params.TestChainConfig,
}
cfg := DefaultConfig()
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, cfg)
if err != nil {
t.Fatalf("failed to create blockchain: %v", err)
}
defer chain.Stop()

// Create test witness data
testHash := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
testWitness1 := []byte("test witness data 1")
testWitness2 := []byte("test witness data 2 - different")

// Test 1: Write witness and verify it's cached
rawdb.WriteWitness(chain.db, testHash, testWitness1)
// Manually add to cache to simulate what happens during block import
chain.witnessCache.Add(testHash, testWitness1)

// Verify witness can be retrieved from cache
retrieved := chain.GetWitness(testHash)
require.NotNil(t, retrieved, "witness should be retrieved")
require.Equal(t, testWitness1, retrieved, "retrieved witness should match written witness")

// Test 2: Verify cache hit (witness should be in cache, not read from DB)
// We can verify this by checking the cache directly
cached, ok := chain.witnessCache.Get(testHash)
require.True(t, ok, "witness should be in cache")
require.Equal(t, testWitness1, cached, "cached witness should match")

// Test 3: Update witness in cache and verify GetWitness returns cached version
chain.witnessCache.Add(testHash, testWitness2)
retrieved = chain.GetWitness(testHash)
require.Equal(t, testWitness2, retrieved, "GetWitness should return cached version")

// Test 4: Test cache miss - witness not in cache but in DB
testHash2 := common.HexToHash("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")
testWitness3 := []byte("test witness data 3")
rawdb.WriteWitness(chain.db, testHash2, testWitness3)
// Don't add to cache - simulate cache miss

// GetWitness should read from DB and cache it
retrieved = chain.GetWitness(testHash2)
require.NotNil(t, retrieved, "witness should be retrieved from DB")
require.Equal(t, testWitness3, retrieved, "retrieved witness should match DB witness")

// Verify it's now in cache
cached, ok = chain.witnessCache.Get(testHash2)
require.True(t, ok, "witness should be cached after GetWitness")
require.Equal(t, testWitness3, cached, "cached witness should match")

// Test 5: Test non-existent witness
nonExistentHash := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000")
retrieved = chain.GetWitness(nonExistentHash)
require.Nil(t, retrieved, "non-existent witness should return nil")

// Test 6: Test cache purge
chain.witnessCache.Purge()
_, ok = chain.witnessCache.Get(testHash)
require.False(t, ok, "witness should not be in cache after purge")
_, ok = chain.witnessCache.Get(testHash2)
require.False(t, ok, "witness should not be in cache after purge")

// After purge, GetWitness should still work by reading from DB
retrieved = chain.GetWitness(testHash2)
require.NotNil(t, retrieved, "witness should still be retrievable from DB after cache purge")
require.Equal(t, testWitness3, retrieved, "retrieved witness should match DB witness")

// Test 7: Test that witness is cached during block import
// Create a block and witness, then import it
testChain, testBlock, testWitness := createTestBlockAndWitness(t)
defer testChain.Stop()

// Import the block with witness - this should cache the witness
blockHash := testBlock.Hash()
_, err = testChain.InsertChainStateless(types.Blocks{testBlock}, []*stateless.Witness{testWitness})
require.NoError(t, err, "block import should succeed")

// Verify witness is in cache after import
cached, ok = testChain.witnessCache.Get(blockHash)
require.True(t, ok, "witness should be cached after block import")
require.NotNil(t, cached, "cached witness should not be nil")

// Verify GetWitness retrieves from cache
retrieved = testChain.GetWitness(blockHash)
require.NotNil(t, retrieved, "witness should be retrievable after import")
require.Equal(t, cached, retrieved, "GetWitness should return cached witness")
}
6 changes: 3 additions & 3 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ func (b *EthAPIBackend) GetWitnesses(ctx context.Context, originBlock uint64, to
return nil, err
}

rlpEncodedWitness := rawdb.ReadWitness(b.eth.blockchain.DB(), blockHeader.Hash())
rlpEncodedWitness := b.eth.blockchain.GetWitness(blockHeader.Hash())

witness, err := stateless.GetWitnessFromRlp(rlpEncodedWitness)
if err != nil {
Expand Down Expand Up @@ -655,7 +655,7 @@ func (b *EthAPIBackend) WitnessByNumber(ctx context.Context, number rpc.BlockNum
return nil, nil
}

rlpEncodedWitness := rawdb.ReadWitness(b.eth.blockchain.DB(), blockHeader.Hash())
rlpEncodedWitness := b.eth.blockchain.GetWitness(blockHeader.Hash())
if len(rlpEncodedWitness) == 0 {
return nil, nil
}
Expand All @@ -681,7 +681,7 @@ func (b *EthAPIBackend) WitnessByHash(ctx context.Context, hash common.Hash) (*s
return nil, nil
}

rlpEncodedWitness := rawdb.ReadWitness(b.eth.blockchain.DB(), hash)
rlpEncodedWitness := b.eth.blockchain.GetWitness(hash)
if len(rlpEncodedWitness) == 0 {
return nil, nil
}
Expand Down
3 changes: 2 additions & 1 deletion eth/handler_wit.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ func (h *witHandler) handleGetWitness(peer *wit.Peer, req *wit.GetWitnessPacket)
if cachedRLPBytes, exists := witnessCache[witnessPage.Hash]; exists {
witnessBytes = cachedRLPBytes
} else {
queriedBytes := rawdb.ReadWitness(h.Chain().DB(), witnessPage.Hash)
// Use GetWitness to benefit from the blockchain's witness cache
queriedBytes := h.Chain().GetWitness(witnessPage.Hash)
witnessCache[witnessPage.Hash] = queriedBytes
witnessBytes = queriedBytes
totalCached += len(queriedBytes)
Expand Down
Loading