diff --git a/cmd/rpcdaemon/rpcdaemontest/test_util.go b/cmd/rpcdaemon/rpcdaemontest/test_util.go
index b1150762426815fa4cd82bcf721be3b0c726c044..4df38906345921b8d6c5cd1b3ebb302fa4958493 100644
--- a/cmd/rpcdaemon/rpcdaemontest/test_util.go
+++ b/cmd/rpcdaemon/rpcdaemontest/test_util.go
@@ -2,7 +2,9 @@ package rpcdaemontest
 
 import (
 	"context"
+	"crypto/ecdsa"
 	"encoding/binary"
+	"github.com/ledgerwatch/erigon/consensus"
 	"math/big"
 	"net"
 	"testing"
@@ -33,8 +35,16 @@ func CreateTestKV(t *testing.T) kv.RwDB {
 	return s.DB
 }
 
-func CreateTestSentry(t *testing.T) (*stages.MockSentry, *core.ChainPack, []*core.ChainPack) {
-	// Configure and generate a sample block chain
+type testAddresses struct {
+	key      *ecdsa.PrivateKey
+	key1     *ecdsa.PrivateKey
+	key2     *ecdsa.PrivateKey
+	address  common.Address
+	address1 common.Address
+	address2 common.Address
+}
+
+func makeTestAddresses() testAddresses {
 	var (
 		key, _   = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
 		key1, _  = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
@@ -42,8 +52,29 @@ func CreateTestSentry(t *testing.T) (*stages.MockSentry, *core.ChainPack, []*cor
 		address  = crypto.PubkeyToAddress(key.PublicKey)
 		address1 = crypto.PubkeyToAddress(key1.PublicKey)
 		address2 = crypto.PubkeyToAddress(key2.PublicKey)
-		theAddr  = common.Address{1}
-		gspec    = &core.Genesis{
+	)
+
+	return testAddresses{
+		key:      key,
+		key1:     key1,
+		key2:     key2,
+		address:  address,
+		address1: address1,
+		address2: address2,
+	}
+}
+
+func CreateTestSentry(t *testing.T) (*stages.MockSentry, *core.ChainPack, []*core.ChainPack) {
+	addresses := makeTestAddresses()
+	var (
+		key      = addresses.key
+		address  = addresses.address
+		address1 = addresses.address1
+		address2 = addresses.address2
+	)
+
+	var (
+		gspec = &core.Genesis{
 			Config: params.AllEthashProtocolChanges,
 			Alloc: core.GenesisAlloc{
 				address:  {Balance: big.NewInt(9000000000000000000)},
@@ -52,30 +83,80 @@ func CreateTestSentry(t *testing.T) (*stages.MockSentry, *core.ChainPack, []*cor
 			},
 			GasLimit: 10000000,
 		}
-		chainId = big.NewInt(1337)
-		// this code generates a log
-		signer = types.LatestSignerForChainID(nil)
 	)
 	m := stages.MockWithGenesis(t, gspec, key)
 
 	contractBackend := backends.NewSimulatedBackendWithConfig(gspec.Alloc, gspec.Config, gspec.GasLimit)
 	defer contractBackend.Close()
 
-	transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, chainId)
-	transactOpts1, _ := bind.NewKeyedTransactorWithChainID(key1, chainId)
-	transactOpts2, _ := bind.NewKeyedTransactorWithChainID(key2, chainId)
-	var poly *contracts.Poly
-
-	var err error
-	var tokenContract *contracts.Token
 	// Generate empty chain to have some orphaned blocks for tests
 	orphanedChain, err := core.GenerateChain(m.ChainConfig, m.Genesis, m.Engine, m.DB, 5, func(i int, block *core.BlockGen) {
 	}, true)
 	if err != nil {
 		t.Fatal(err)
 	}
-	// We generate the blocks without plainstant because it's not supported in core.GenerateChain
-	chain, err := core.GenerateChain(m.ChainConfig, m.Genesis, m.Engine, m.DB, 10, func(i int, block *core.BlockGen) {
+
+	chain, err := getChainInstance(&addresses, m.ChainConfig, m.Genesis, m.Engine, m.DB, contractBackend)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err = m.InsertChain(orphanedChain); err != nil {
+		t.Fatal(err)
+	}
+	if err = m.InsertChain(chain); err != nil {
+		t.Fatal(err)
+	}
+
+	return m, chain, []*core.ChainPack{orphanedChain}
+}
+
+var chainInstance *core.ChainPack
+
+func getChainInstance(
+	addresses *testAddresses,
+	config *params.ChainConfig,
+	parent *types.Block,
+	engine consensus.Engine,
+	db kv.RwDB,
+	contractBackend *backends.SimulatedBackend,
+) (*core.ChainPack, error) {
+	var err error
+	if chainInstance == nil {
+		chainInstance, err = generateChain(addresses, config, parent, engine, db, contractBackend)
+	}
+	return chainInstance.Copy(), err
+}
+
+func generateChain(
+	addresses *testAddresses,
+	config *params.ChainConfig,
+	parent *types.Block,
+	engine consensus.Engine,
+	db kv.RwDB,
+	contractBackend *backends.SimulatedBackend,
+) (*core.ChainPack, error) {
+	var (
+		key      = addresses.key
+		key1     = addresses.key1
+		key2     = addresses.key2
+		address  = addresses.address
+		address1 = addresses.address1
+		address2 = addresses.address2
+		theAddr  = common.Address{1}
+		chainId  = big.NewInt(1337)
+		// this code generates a log
+		signer = types.LatestSignerForChainID(nil)
+	)
+
+	transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, chainId)
+	transactOpts1, _ := bind.NewKeyedTransactorWithChainID(key1, chainId)
+	transactOpts2, _ := bind.NewKeyedTransactorWithChainID(key2, chainId)
+	var poly *contracts.Poly
+	var tokenContract *contracts.Token
+
+	// We generate the blocks without plain state because it's not supported in core.GenerateChain
+	return core.GenerateChain(config, parent, engine, db, 10, func(i int, block *core.BlockGen) {
 		var (
 			txn types.Transaction
 			txs []types.Transaction
@@ -193,18 +274,6 @@ func CreateTestSentry(t *testing.T) (*stages.MockSentry, *core.ChainPack, []*cor
 		}
 		contractBackend.Commit()
 	}, true)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err = m.InsertChain(orphanedChain); err != nil {
-		t.Fatal(err)
-	}
-	if err = m.InsertChain(chain); err != nil {
-		t.Fatal(err)
-	}
-
-	return m, chain, []*core.ChainPack{orphanedChain}
 }
 
 type IsMiningMock struct{}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index 94473dadb8bac021d4a866d430f6fe2b118cc720..24a8e4266df02ccc35f9ec56c88af5032b4ac899 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -238,6 +238,38 @@ func (cp ChainPack) Slice(i, j int) *ChainPack {
 	}
 }
 
+// Copy creates a deep copy of the ChainPack.
+func (cp *ChainPack) Copy() *ChainPack {
+	headers := make([]*types.Header, 0, len(cp.Headers))
+	for _, header := range cp.Headers {
+		headers = append(headers, types.CopyHeader(header))
+	}
+
+	blocks := make([]*types.Block, 0, len(cp.Blocks))
+	for _, block := range cp.Blocks {
+		blocks = append(blocks, block.Copy())
+	}
+
+	receipts := make([]types.Receipts, 0, len(cp.Receipts))
+	for _, receiptList := range cp.Receipts {
+		receiptListCopy := make(types.Receipts, 0, len(receiptList))
+		for _, receipt := range receiptList {
+			receiptListCopy = append(receiptListCopy, receipt.Copy())
+		}
+		receipts = append(receipts, receiptListCopy)
+	}
+
+	topBlock := cp.TopBlock.Copy()
+
+	return &ChainPack{
+		Length:   cp.Length,
+		Headers:  headers,
+		Blocks:   blocks,
+		Receipts: receipts,
+		TopBlock: topBlock,
+	}
+}
+
 // GenerateChain creates a chain of n blocks. The first block's
 // parent will be the provided parent. db is used to store
 // intermediate states and should contain the parent's state trie.
diff --git a/core/types/block.go b/core/types/block.go
index c89910e0a92b986f9f8647fa05fe0d697910bcb7..ae83134f35fdcd25efb05c3352fe4d49549f11b4 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -1280,6 +1280,52 @@ func CalcUncleHash(uncles []*Header) common.Hash {
 	return rlpHash(uncles)
 }
 
+// Copy creates a deep copy of the Block.
+func (b *Block) Copy() *Block {
+	uncles := make([]*Header, 0, len(b.uncles))
+	for _, uncle := range b.uncles {
+		uncles = append(uncles, CopyHeader(uncle))
+	}
+
+	transactionsData, err := MarshalTransactionsBinary(b.transactions)
+	if err != nil {
+		panic(fmt.Errorf("MarshalTransactionsBinary failed: %w", err))
+	}
+	transactions, err := DecodeTransactions(transactionsData)
+	if err != nil {
+		panic(fmt.Errorf("DecodeTransactions failed: %w", err))
+	}
+
+	var hashValue atomic.Value
+	if value := b.hash.Load(); value != nil {
+		hash := value.(common.Hash)
+		hashCopy := common.BytesToHash(hash.Bytes())
+		hashValue.Store(hashCopy)
+	}
+
+	var sizeValue atomic.Value
+	if size := b.size.Load(); size != nil {
+		sizeValue.Store(size)
+	}
+
+	td := big.NewInt(0).Set(b.td)
+
+	if b.ReceivedFrom != nil {
+		panic("ReceivedFrom deep copy is not supported")
+	}
+
+	return &Block{
+		header:       CopyHeader(b.header),
+		uncles:       uncles,
+		transactions: transactions,
+		hash:         hashValue,
+		size:         sizeValue,
+		td:           td,
+		ReceivedAt:   b.ReceivedAt,
+		ReceivedFrom: nil,
+	}
+}
+
 // WithSeal returns a new block with the data from b but the header replaced with
 // the sealed one.
 func (b *Block) WithSeal(header *Header) *Block {
diff --git a/core/types/log.go b/core/types/log.go
index 21c2f2725e4a51b10a06f9c53d84282f343e85ad..29ea8dafc46fe836c4ee089155239c2e885f3fdd 100644
--- a/core/types/log.go
+++ b/core/types/log.go
@@ -100,6 +100,30 @@ func (l *Log) DecodeRLP(s *rlp.Stream) error {
 	return err
 }
 
+// Copy creates a deep copy of the Log.
+func (l *Log) Copy() *Log {
+	topics := make([]common.Hash, 0, len(l.Topics))
+	for _, topic := range l.Topics {
+		topicCopy := common.BytesToHash(topic.Bytes())
+		topics = append(topics, topicCopy)
+	}
+
+	data := make([]byte, len(l.Data))
+	copy(data, l.Data)
+
+	return &Log{
+		Address:     common.BytesToAddress(l.Address.Bytes()),
+		Topics:      topics,
+		Data:        data,
+		BlockNumber: l.BlockNumber,
+		TxHash:      common.BytesToHash(l.TxHash.Bytes()),
+		TxIndex:     l.TxIndex,
+		BlockHash:   common.BytesToHash(l.BlockHash.Bytes()),
+		Index:       l.Index,
+		Removed:     l.Removed,
+	}
+}
+
 // LogForStorage is a wrapper around a Log that flattens and parses the entire content of
 // a log including non-consensus fields.
 type LogForStorage Log
diff --git a/core/types/receipt.go b/core/types/receipt.go
index 55451350503af2cf0c964fc5a930f7de2844f10b..14fb4d73d22a357456f75926daf64471ca2d7db7 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -297,6 +297,39 @@ func (r *Receipt) Size() common.StorageSize {
 	return size
 }
 
+// Copy creates a deep copy of the Receipt.
+func (r *Receipt) Copy() *Receipt {
+	postState := make([]byte, len(r.PostState))
+	copy(postState, r.PostState)
+
+	bloom := BytesToBloom(r.Bloom.Bytes())
+
+	logs := make(Logs, 0, len(r.Logs))
+	for _, log := range r.Logs {
+		logs = append(logs, log.Copy())
+	}
+
+	txHash := common.BytesToHash(r.TxHash.Bytes())
+	contractAddress := common.BytesToAddress(r.ContractAddress.Bytes())
+	blockHash := common.BytesToHash(r.BlockHash.Bytes())
+	blockNumber := big.NewInt(0).Set(r.BlockNumber)
+
+	return &Receipt{
+		Type:              r.Type,
+		PostState:         postState,
+		Status:            r.Status,
+		CumulativeGasUsed: r.CumulativeGasUsed,
+		Bloom:             bloom,
+		Logs:              logs,
+		TxHash:            txHash,
+		ContractAddress:   contractAddress,
+		GasUsed:           r.GasUsed,
+		BlockHash:         blockHash,
+		BlockNumber:       blockNumber,
+		TransactionIndex:  r.TransactionIndex,
+	}
+}
+
 type ReceiptsForStorage []*ReceiptForStorage
 
 // ReceiptForStorage is a wrapper around a Receipt that flattens and parses the