From aad0d0c777337cd35006f9f16bcb484099e01116 Mon Sep 17 00:00:00 2001
From: Alex Sharov <AskAlexSharov@gmail.com>
Date: Sun, 14 Nov 2021 11:08:52 +0700
Subject: [PATCH] grpc GetBlock api (#2955)

---
 cmd/rpcdaemon/cli/config.go                   | 28 +++---
 cmd/rpcdaemon/commands/call_traces_test.go    |  9 +-
 .../commands/corner_cases_support_test.go     |  5 +-
 cmd/rpcdaemon/commands/daemon.go              |  4 +-
 cmd/rpcdaemon/commands/debug_api_test.go      |  9 +-
 cmd/rpcdaemon/commands/eth_api.go             | 15 +++-
 cmd/rpcdaemon/commands/eth_api_test.go        | 23 ++---
 cmd/rpcdaemon/commands/eth_call_test.go       |  5 +-
 cmd/rpcdaemon/commands/eth_ming_test.go       |  3 +-
 cmd/rpcdaemon/commands/eth_subscribe_test.go  |  2 +-
 .../commands/send_transaction_test.go         |  3 +-
 cmd/rpcdaemon/commands/trace_adhoc_test.go    |  9 +-
 cmd/rpcdaemon/commands/txpool_api_test.go     |  3 +-
 cmd/rpcdaemon/interfaces/interfaces.go        | 13 +++
 cmd/rpcdaemon/main.go                         |  4 +-
 cmd/rpcdaemon/rpcdaemontest/test_util.go      |  2 +-
 cmd/rpcdaemon/services/eth_backend.go         | 29 +++++-
 eth/backend.go                                |  2 +-
 ethdb/privateapi/ethbackend.go                | 36 ++++++--
 go.mod                                        |  4 +-
 go.sum                                        |  6 +-
 rpc/client_test.go                            |  6 +-
 rpc/service.go                                |  8 +-
 turbo/adapter/block_getter.go                 | 24 -----
 turbo/adapter/chain_context.go                | 90 -------------------
 turbo/snapshotsync/block_reader.go            | 27 ++++++
 26 files changed, 187 insertions(+), 182 deletions(-)
 create mode 100644 cmd/rpcdaemon/interfaces/interfaces.go
 delete mode 100644 turbo/adapter/block_getter.go
 delete mode 100644 turbo/adapter/chain_context.go
 create mode 100644 turbo/snapshotsync/block_reader.go

diff --git a/cmd/rpcdaemon/cli/config.go b/cmd/rpcdaemon/cli/config.go
index a540519cd3..b344eaf91f 100644
--- a/cmd/rpcdaemon/cli/config.go
+++ b/cmd/rpcdaemon/cli/config.go
@@ -19,12 +19,14 @@ import (
 	"github.com/ledgerwatch/erigon-lib/kv/remotedb"
 	"github.com/ledgerwatch/erigon-lib/kv/remotedbserver"
 	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/health"
+	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/interfaces"
 	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/services"
 	"github.com/ledgerwatch/erigon/cmd/utils"
 	"github.com/ledgerwatch/erigon/common/paths"
 	"github.com/ledgerwatch/erigon/internal/debug"
 	"github.com/ledgerwatch/erigon/node"
 	"github.com/ledgerwatch/erigon/rpc"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/ledgerwatch/log/v3"
 	"github.com/spf13/cobra"
 	"google.golang.org/grpc"
@@ -213,23 +215,25 @@ func checkDbCompatibility(ctx context.Context, db kv.RoDB) error {
 	return nil
 }
 
-func RemoteServices(ctx context.Context, cfg Flags, logger log.Logger, rootCancel context.CancelFunc) (db kv.RoDB, eth services.ApiBackend, txPool *services.TxPoolService, mining *services.MiningService, stateCache kvcache.Cache, err error) {
+func RemoteServices(ctx context.Context, cfg Flags, logger log.Logger, rootCancel context.CancelFunc) (db kv.RoDB, eth services.ApiBackend, txPool *services.TxPoolService, mining *services.MiningService, stateCache kvcache.Cache, blockReader interfaces.BlockReader, err error) {
 	if !cfg.SingleNodeMode && cfg.PrivateApiAddr == "" {
-		return nil, nil, nil, nil, nil, fmt.Errorf("either remote db or local db must be specified")
+		return nil, nil, nil, nil, nil, nil, fmt.Errorf("either remote db or local db must be specified")
 	}
+
 	// Do not change the order of these checks. Chaindata needs to be checked first, because PrivateApiAddr has default value which is not ""
 	// If PrivateApiAddr is checked first, the Chaindata option will never work
 	if cfg.SingleNodeMode {
 		var rwKv kv.RwDB
 		rwKv, err = kv2.NewMDBX(logger).Path(cfg.Chaindata).Readonly().Open()
 		if err != nil {
-			return nil, nil, nil, nil, nil, err
+			return nil, nil, nil, nil, nil, nil, err
 		}
 		if compatErr := checkDbCompatibility(ctx, rwKv); compatErr != nil {
-			return nil, nil, nil, nil, nil, compatErr
+			return nil, nil, nil, nil, nil, nil, compatErr
 		}
 		db = rwKv
 		stateCache = kvcache.NewDummy()
+		blockReader = snapshotsync.NewBlockReader()
 	} else {
 		if cfg.StateCache.KeysLimit > 0 {
 			stateCache = kvcache.New(cfg.StateCache)
@@ -240,32 +244,34 @@ func RemoteServices(ctx context.Context, cfg Flags, logger log.Logger, rootCance
 	}
 
 	if cfg.PrivateApiAddr == "" {
-		return db, eth, txPool, mining, stateCache, nil
+		return db, eth, txPool, mining, stateCache, blockReader, nil
 	}
 
 	creds, err := grpcutil.TLS(cfg.TLSCACert, cfg.TLSCertfile, cfg.TLSKeyFile)
 	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("open tls cert: %w", err)
+		return nil, nil, nil, nil, nil, nil, fmt.Errorf("open tls cert: %w", err)
 	}
 	conn, err := grpcutil.Connect(creds, cfg.PrivateApiAddr)
 	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("could not connect to execution service privateApi: %w", err)
+		return nil, nil, nil, nil, nil, nil, fmt.Errorf("could not connect to execution service privateApi: %w", err)
 	}
 
 	kvClient := remote.NewKVClient(conn)
 	remoteKv, err := remotedb.NewRemote(gointerfaces.VersionFromProto(remotedbserver.KvServiceAPIVersion), logger, kvClient).Open()
 	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("could not connect to remoteKv: %w", err)
+		return nil, nil, nil, nil, nil, nil, fmt.Errorf("could not connect to remoteKv: %w", err)
 	}
 
 	subscribeToStateChangesLoop(ctx, kvClient, stateCache)
 
-	remoteEth := services.NewRemoteBackend(conn)
+	remoteEth := services.NewRemoteBackend(conn, db)
+	blockReader = remoteEth
+
 	txpoolConn := conn
 	if cfg.TxPoolV2 {
 		txpoolConn, err = grpcutil.Connect(creds, cfg.TxPoolApiAddr)
 		if err != nil {
-			return nil, nil, nil, nil, nil, fmt.Errorf("could not connect to txpool api: %w", err)
+			return nil, nil, nil, nil, nil, nil, fmt.Errorf("could not connect to txpool api: %w", err)
 		}
 	}
 	mining = services.NewMiningService(txpoolConn)
@@ -288,7 +294,7 @@ func RemoteServices(ctx context.Context, cfg Flags, logger log.Logger, rootCance
 			rootCancel()
 		}
 	}()
-	return db, eth, txPool, mining, stateCache, err
+	return db, eth, txPool, mining, stateCache, blockReader, err
 }
 
 func StartRpcServer(ctx context.Context, cfg Flags, rpcAPI []rpc.API) error {
diff --git a/cmd/rpcdaemon/commands/call_traces_test.go b/cmd/rpcdaemon/commands/call_traces_test.go
index f96f7e65b6..95ebddb27e 100644
--- a/cmd/rpcdaemon/commands/call_traces_test.go
+++ b/cmd/rpcdaemon/commands/call_traces_test.go
@@ -11,6 +11,7 @@ import (
 	"github.com/ledgerwatch/erigon/common"
 	"github.com/ledgerwatch/erigon/common/hexutil"
 	"github.com/ledgerwatch/erigon/core"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/ledgerwatch/erigon/turbo/stages"
 	"github.com/ledgerwatch/log/v3"
 	"github.com/stretchr/testify/assert"
@@ -50,7 +51,9 @@ func TestCallTraceOneByOne(t *testing.T) {
 	if err != nil {
 		t.Fatalf("generate chain: %v", err)
 	}
-	api := NewTraceAPI(NewBaseApi(nil, kvcache.New(kvcache.DefaultCoherentConfig), false), m.DB, &cli.Flags{})
+	api := NewTraceAPI(
+		NewBaseApi(nil, kvcache.New(kvcache.DefaultCoherentConfig), snapshotsync.NewBlockReader(), false),
+		m.DB, &cli.Flags{})
 	// Insert blocks 1 by 1, to tirgget possible "off by one" errors
 	for i := 0; i < chain.Length; i++ {
 		if err = m.InsertChain(chain.Slice(i, i+1)); err != nil {
@@ -95,7 +98,7 @@ func TestCallTraceUnwind(t *testing.T) {
 	if err != nil {
 		t.Fatalf("generate chainB: %v", err)
 	}
-	api := NewTraceAPI(NewBaseApi(nil, kvcache.New(kvcache.DefaultCoherentConfig), false), m.DB, &cli.Flags{})
+	api := NewTraceAPI(NewBaseApi(nil, kvcache.New(kvcache.DefaultCoherentConfig), snapshotsync.NewBlockReader(), false), m.DB, &cli.Flags{})
 	if err = m.InsertChain(chainA); err != nil {
 		t.Fatalf("inserting chainA: %v", err)
 	}
@@ -155,7 +158,7 @@ func TestFilterNoAddresses(t *testing.T) {
 	if err != nil {
 		t.Fatalf("generate chain: %v", err)
 	}
-	api := NewTraceAPI(NewBaseApi(nil, kvcache.New(kvcache.DefaultCoherentConfig), false), m.DB, &cli.Flags{})
+	api := NewTraceAPI(NewBaseApi(nil, kvcache.New(kvcache.DefaultCoherentConfig), snapshotsync.NewBlockReader(), false), m.DB, &cli.Flags{})
 	// Insert blocks 1 by 1, to tirgget possible "off by one" errors
 	for i := 0; i < chain.Length; i++ {
 		if err = m.InsertChain(chain.Slice(i, i+1)); err != nil {
diff --git a/cmd/rpcdaemon/commands/corner_cases_support_test.go b/cmd/rpcdaemon/commands/corner_cases_support_test.go
index e4dd2c95f2..1ab11b66ca 100644
--- a/cmd/rpcdaemon/commands/corner_cases_support_test.go
+++ b/cmd/rpcdaemon/commands/corner_cases_support_test.go
@@ -8,6 +8,7 @@ import (
 	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/rpcdaemontest"
 	"github.com/ledgerwatch/erigon/common"
 	"github.com/ledgerwatch/erigon/rpc"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/stretchr/testify/require"
 )
 
@@ -18,7 +19,9 @@ func TestNotFoundMustReturnNil(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	defer db.Close()
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(
+		NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false),
+		db, nil, nil, nil, 5000000)
 	ctx := context.Background()
 
 	a, err := api.GetTransactionByBlockNumberAndIndex(ctx, 10_000, 1)
diff --git a/cmd/rpcdaemon/commands/daemon.go b/cmd/rpcdaemon/commands/daemon.go
index a32f54c466..674ed8e145 100644
--- a/cmd/rpcdaemon/commands/daemon.go
+++ b/cmd/rpcdaemon/commands/daemon.go
@@ -8,6 +8,7 @@ import (
 	"github.com/ledgerwatch/erigon-lib/kv/kvcache"
 	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/cli"
 	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/filters"
+	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/interfaces"
 	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/services"
 	"github.com/ledgerwatch/erigon/rpc"
 )
@@ -16,10 +17,11 @@ import (
 func APIList(ctx context.Context, db kv.RoDB,
 	eth services.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient, filters *filters.Filters,
 	stateCache kvcache.Cache,
+	blockReader interfaces.BlockReader,
 	cfg cli.Flags, customAPIList []rpc.API) []rpc.API {
 	var defaultAPIList []rpc.API
 
-	base := NewBaseApi(filters, stateCache, cfg.SingleNodeMode)
+	base := NewBaseApi(filters, stateCache, blockReader, cfg.SingleNodeMode)
 	if cfg.TevmEnabled {
 		base.EnableTevmExperiment()
 	}
diff --git a/cmd/rpcdaemon/commands/debug_api_test.go b/cmd/rpcdaemon/commands/debug_api_test.go
index a0eff1db45..cbba7364c0 100644
--- a/cmd/rpcdaemon/commands/debug_api_test.go
+++ b/cmd/rpcdaemon/commands/debug_api_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/ledgerwatch/erigon/common"
 	"github.com/ledgerwatch/erigon/eth/tracers"
 	"github.com/ledgerwatch/erigon/internal/ethapi"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 )
 
 var debugTraceTransactionTests = []struct {
@@ -39,7 +40,9 @@ var debugTraceTransactionNoRefundTests = []struct {
 func TestTraceTransaction(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewPrivateDebugAPI(NewBaseApi(nil, stateCache, false), db, 0)
+	api := NewPrivateDebugAPI(
+		NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false),
+		db, 0)
 	for _, tt := range debugTraceTransactionTests {
 		var buf bytes.Buffer
 		stream := jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096)
@@ -69,7 +72,9 @@ func TestTraceTransaction(t *testing.T) {
 func TestTraceTransactionNoRefund(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewPrivateDebugAPI(NewBaseApi(nil, stateCache, false), db, 0)
+	api := NewPrivateDebugAPI(
+		NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false),
+		db, 0)
 	for _, tt := range debugTraceTransactionNoRefundTests {
 		var buf bytes.Buffer
 		stream := jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096)
diff --git a/cmd/rpcdaemon/commands/eth_api.go b/cmd/rpcdaemon/commands/eth_api.go
index 6647b05e86..b960c0b77b 100644
--- a/cmd/rpcdaemon/commands/eth_api.go
+++ b/cmd/rpcdaemon/commands/eth_api.go
@@ -12,6 +12,7 @@ import (
 	"github.com/ledgerwatch/erigon-lib/kv"
 	"github.com/ledgerwatch/erigon-lib/kv/kvcache"
 	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/filters"
+	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/interfaces"
 	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/services"
 	"github.com/ledgerwatch/erigon/common"
 	"github.com/ledgerwatch/erigon/common/hexutil"
@@ -105,10 +106,11 @@ type BaseAPI struct {
 	_genesis     *types.Block
 	_genesisLock sync.RWMutex
 
-	TevmEnabled bool // experiment
+	_blockReader interfaces.BlockReader
+	TevmEnabled  bool // experiment
 }
 
-func NewBaseApi(f *filters.Filters, stateCache kvcache.Cache, singleNodeMode bool) *BaseAPI {
+func NewBaseApi(f *filters.Filters, stateCache kvcache.Cache, blockReader interfaces.BlockReader, singleNodeMode bool) *BaseAPI {
 	blocksLRUSize := 128 // ~32Mb
 	if !singleNodeMode {
 		blocksLRUSize = 512
@@ -118,7 +120,7 @@ func NewBaseApi(f *filters.Filters, stateCache kvcache.Cache, singleNodeMode boo
 		panic(err)
 	}
 
-	return &BaseAPI{filters: f, stateCache: stateCache, blocksLRU: blocksLRU}
+	return &BaseAPI{filters: f, stateCache: stateCache, blocksLRU: blocksLRU, _blockReader: blockReader}
 }
 
 func (api *BaseAPI) chainConfig(tx kv.Tx) (*params.ChainConfig, error) {
@@ -159,13 +161,18 @@ func (api *BaseAPI) blockWithSenders(tx kv.Tx, hash common.Hash, number uint64)
 			return it.(*types.Block), nil
 		}
 	}
-	block, _, err := rawdb.ReadBlockWithSenders(tx, hash, number)
+	block, _, err := api._blockReader.BlockWithSenders(context.Background(), tx, hash, number)
 	if err != nil {
 		return nil, err
 	}
 	if block == nil { // don't save nil's to cache
 		return nil, nil
 	}
+	// don't save empty blocks to cache, because in Erigon
+	// if block become non-canonical - we remove it's transactions, but block can become canonical in future
+	if block.Transactions().Len() == 0 {
+		return block, nil
+	}
 	if api.blocksLRU != nil {
 		api.blocksLRU.Add(hash, block)
 	}
diff --git a/cmd/rpcdaemon/commands/eth_api_test.go b/cmd/rpcdaemon/commands/eth_api_test.go
index 7b31dc7f8a..de678d383e 100644
--- a/cmd/rpcdaemon/commands/eth_api_test.go
+++ b/cmd/rpcdaemon/commands/eth_api_test.go
@@ -8,6 +8,7 @@ import (
 	"github.com/ledgerwatch/erigon/core"
 	"github.com/ledgerwatch/erigon/internal/ethapi"
 	"github.com/ledgerwatch/erigon/rpc"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/stretchr/testify/assert"
 
 	"github.com/ledgerwatch/erigon-lib/kv/kvcache"
@@ -18,7 +19,7 @@ import (
 func TestGetTransactionReceipt(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	// Call GetTransactionReceipt for transaction which is not in the database
 	if _, err := api.GetTransactionReceipt(context.Background(), common.Hash{}); err != nil {
 		t.Errorf("calling GetTransactionReceipt with empty hash: %v", err)
@@ -28,7 +29,7 @@ func TestGetTransactionReceipt(t *testing.T) {
 func TestGetTransactionReceiptUnprotected(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	// Call GetTransactionReceipt for un-protected transaction
 	if _, err := api.GetTransactionReceipt(context.Background(), common.HexToHash("0x3f3cb8a0e13ed2481f97f53f7095b9cbc78b6ffb779f2d3e565146371a8830ea")); err != nil {
 		t.Errorf("calling GetTransactionReceipt for unprotected tx: %v", err)
@@ -41,7 +42,7 @@ func TestGetStorageAt_ByBlockNumber_WithRequireCanonicalDefault(t *testing.T) {
 	assert := assert.New(t)
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	addr := common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 
 	result, err := api.GetStorageAt(context.Background(), addr, "0x0", rpc.BlockNumberOrHashWithNumber(0))
@@ -57,7 +58,7 @@ func TestGetStorageAt_ByBlockHash_WithRequireCanonicalDefault(t *testing.T) {
 	m, _, _ := rpcdaemontest.CreateTestSentry(t)
 	db := m.DB
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	addr := common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 
 	result, err := api.GetStorageAt(context.Background(), addr, "0x0", rpc.BlockNumberOrHashWithHash(m.Genesis.Hash(), false))
@@ -73,7 +74,7 @@ func TestGetStorageAt_ByBlockHash_WithRequireCanonicalTrue(t *testing.T) {
 	m, _, _ := rpcdaemontest.CreateTestSentry(t)
 	db := m.DB
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	addr := common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 
 	result, err := api.GetStorageAt(context.Background(), addr, "0x0", rpc.BlockNumberOrHashWithHash(m.Genesis.Hash(), true))
@@ -88,7 +89,7 @@ func TestGetStorageAt_ByBlockHash_WithRequireCanonicalDefault_BlockNotFoundError
 	m, _, _ := rpcdaemontest.CreateTestSentry(t)
 	db := m.DB
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	addr := common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 
 	offChain, err := core.GenerateChain(m.ChainConfig, m.Genesis, m.Engine, m.DB, 1, func(i int, block *core.BlockGen) {
@@ -111,7 +112,7 @@ func TestGetStorageAt_ByBlockHash_WithRequireCanonicalTrue_BlockNotFoundError(t
 	m, _, _ := rpcdaemontest.CreateTestSentry(t)
 	db := m.DB
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	addr := common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 
 	offChain, err := core.GenerateChain(m.ChainConfig, m.Genesis, m.Engine, m.DB, 1, func(i int, block *core.BlockGen) {
@@ -135,7 +136,7 @@ func TestGetStorageAt_ByBlockHash_WithRequireCanonicalDefault_NonCanonicalBlock(
 	m, _, orphanedChain := rpcdaemontest.CreateTestSentry(t)
 	db := m.DB
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	addr := common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 
 	orphanedBlock := orphanedChain[0].Blocks[0]
@@ -152,7 +153,7 @@ func TestGetStorageAt_ByBlockHash_WithRequireCanonicalTrue_NonCanonicalBlock(t *
 	m, _, orphanedChain := rpcdaemontest.CreateTestSentry(t)
 	db := m.DB
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	addr := common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 
 	orphanedBlock := orphanedChain[0].Blocks[0]
@@ -170,7 +171,7 @@ func TestCall_ByBlockHash_WithRequireCanonicalDefault_NonCanonicalBlock(t *testi
 	m, _, orphanedChain := rpcdaemontest.CreateTestSentry(t)
 	db := m.DB
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	from := common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 	to := common.HexToAddress("0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e")
 
@@ -195,7 +196,7 @@ func TestCall_ByBlockHash_WithRequireCanonicalTrue_NonCanonicalBlock(t *testing.
 	m, _, orphanedChain := rpcdaemontest.CreateTestSentry(t)
 	db := m.DB
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	from := common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 	to := common.HexToAddress("0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e")
 
diff --git a/cmd/rpcdaemon/commands/eth_call_test.go b/cmd/rpcdaemon/commands/eth_call_test.go
index aee770f868..b0c7ec1602 100644
--- a/cmd/rpcdaemon/commands/eth_call_test.go
+++ b/cmd/rpcdaemon/commands/eth_call_test.go
@@ -10,12 +10,13 @@ import (
 	"github.com/ledgerwatch/erigon/common"
 	"github.com/ledgerwatch/erigon/internal/ethapi"
 	"github.com/ledgerwatch/erigon/rpc"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 )
 
 func TestEstimateGas(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	var from = common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 	var to = common.HexToAddress("0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e")
 	if _, err := api.EstimateGas(context.Background(), ethapi.CallArgs{
@@ -29,7 +30,7 @@ func TestEstimateGas(t *testing.T) {
 func TestEthCallNonCanonical(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(nil, stateCache, false), db, nil, nil, nil, 5000000)
+	api := NewEthAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil, nil, nil, 5000000)
 	var from = common.HexToAddress("0x71562b71999873db5b286df957af199ec94617f7")
 	var to = common.HexToAddress("0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e")
 	if _, err := api.Call(context.Background(), ethapi.CallArgs{
diff --git a/cmd/rpcdaemon/commands/eth_ming_test.go b/cmd/rpcdaemon/commands/eth_ming_test.go
index 35e734ed28..f46e2638ca 100644
--- a/cmd/rpcdaemon/commands/eth_ming_test.go
+++ b/cmd/rpcdaemon/commands/eth_ming_test.go
@@ -11,6 +11,7 @@ import (
 	"github.com/ledgerwatch/erigon/cmd/rpcdaemon/rpcdaemontest"
 	"github.com/ledgerwatch/erigon/core/types"
 	"github.com/ledgerwatch/erigon/rlp"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/ledgerwatch/erigon/turbo/stages"
 	"github.com/stretchr/testify/require"
 )
@@ -20,7 +21,7 @@ func TestPendingBlock(t *testing.T) {
 	mining := txpool.NewMiningClient(conn)
 	ff := filters.New(ctx, nil, nil, mining)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewEthAPI(NewBaseApi(ff, stateCache, false), nil, nil, nil, mining, 5000000)
+	api := NewEthAPI(NewBaseApi(ff, stateCache, snapshotsync.NewBlockReader(), false), nil, nil, nil, mining, 5000000)
 	expect := uint64(12345)
 	b, err := rlp.EncodeToBytes(types.NewBlockWithHeader(&types.Header{Number: big.NewInt(int64(expect))}))
 	require.NoError(t, err)
diff --git a/cmd/rpcdaemon/commands/eth_subscribe_test.go b/cmd/rpcdaemon/commands/eth_subscribe_test.go
index 85bd8dd977..41ff3afbc6 100644
--- a/cmd/rpcdaemon/commands/eth_subscribe_test.go
+++ b/cmd/rpcdaemon/commands/eth_subscribe_test.go
@@ -36,7 +36,7 @@ func TestEthSubscribe(t *testing.T) {
 	m.ReceiveWg.Wait() // Wait for all messages to be processed before we proceeed
 
 	ctx, conn := rpcdaemontest.CreateTestGrpcConn(t, m)
-	backend := services.NewRemoteBackend(conn)
+	backend := services.NewRemoteBackend(conn, m.DB)
 	ff := filters.New(ctx, backend, nil, nil)
 
 	newHeads := make(chan *types.Header)
diff --git a/cmd/rpcdaemon/commands/send_transaction_test.go b/cmd/rpcdaemon/commands/send_transaction_test.go
index 3f0873f59f..e1b39b775f 100644
--- a/cmd/rpcdaemon/commands/send_transaction_test.go
+++ b/cmd/rpcdaemon/commands/send_transaction_test.go
@@ -20,6 +20,7 @@ import (
 	"github.com/ledgerwatch/erigon/eth/protocols/eth"
 	"github.com/ledgerwatch/erigon/params"
 	"github.com/ledgerwatch/erigon/rlp"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/ledgerwatch/erigon/turbo/stages"
 	"github.com/stretchr/testify/require"
 )
@@ -71,7 +72,7 @@ func TestSendRawTransaction(t *testing.T) {
 	txPool := txpool.NewTxpoolClient(conn)
 	ff := filters.New(ctx, nil, txPool, txpool.NewMiningClient(conn))
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := commands.NewEthAPI(commands.NewBaseApi(ff, stateCache, false), m.DB, nil, txPool, nil, 5000000)
+	api := commands.NewEthAPI(commands.NewBaseApi(ff, stateCache, snapshotsync.NewBlockReader(), false), m.DB, nil, txPool, nil, 5000000)
 
 	buf := bytes.NewBuffer(nil)
 	err = txn.MarshalBinary(buf)
diff --git a/cmd/rpcdaemon/commands/trace_adhoc_test.go b/cmd/rpcdaemon/commands/trace_adhoc_test.go
index 050c5b4e70..efa5c53a1b 100644
--- a/cmd/rpcdaemon/commands/trace_adhoc_test.go
+++ b/cmd/rpcdaemon/commands/trace_adhoc_test.go
@@ -13,13 +13,14 @@ import (
 	"github.com/ledgerwatch/erigon/common/hexutil"
 	"github.com/ledgerwatch/erigon/core/rawdb"
 	"github.com/ledgerwatch/erigon/rpc"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/stretchr/testify/require"
 )
 
 func TestEmptyQuery(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewTraceAPI(NewBaseApi(nil, stateCache, false), db, &cli.Flags{})
+	api := NewTraceAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, &cli.Flags{})
 	// Call GetTransactionReceipt for transaction which is not in the database
 	var latest = rpc.LatestBlockNumber
 	results, err := api.CallMany(context.Background(), json.RawMessage("[]"), &rpc.BlockNumberOrHash{BlockNumber: &latest})
@@ -36,7 +37,7 @@ func TestEmptyQuery(t *testing.T) {
 func TestCoinbaseBalance(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewTraceAPI(NewBaseApi(nil, stateCache, false), db, &cli.Flags{})
+	api := NewTraceAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, &cli.Flags{})
 	// Call GetTransactionReceipt for transaction which is not in the database
 	var latest = rpc.LatestBlockNumber
 	results, err := api.CallMany(context.Background(), json.RawMessage(`
@@ -63,7 +64,7 @@ func TestCoinbaseBalance(t *testing.T) {
 func TestReplayTransaction(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewTraceAPI(NewBaseApi(nil, stateCache, false), db, &cli.Flags{})
+	api := NewTraceAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, &cli.Flags{})
 	var txnHash common.Hash
 	if err := db.View(context.Background(), func(tx kv.Tx) error {
 		b, err := rawdb.ReadBlockByNumber(tx, 6)
@@ -91,7 +92,7 @@ func TestReplayTransaction(t *testing.T) {
 func TestReplayBlockTransactions(t *testing.T) {
 	db := rpcdaemontest.CreateTestKV(t)
 	stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
-	api := NewTraceAPI(NewBaseApi(nil, stateCache, false), db, &cli.Flags{})
+	api := NewTraceAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, &cli.Flags{})
 
 	// Call GetTransactionReceipt for transaction which is not in the database
 	n := rpc.BlockNumber(6)
diff --git a/cmd/rpcdaemon/commands/txpool_api_test.go b/cmd/rpcdaemon/commands/txpool_api_test.go
index 95d4a52e21..37cb68187e 100644
--- a/cmd/rpcdaemon/commands/txpool_api_test.go
+++ b/cmd/rpcdaemon/commands/txpool_api_test.go
@@ -16,6 +16,7 @@ import (
 	"github.com/ledgerwatch/erigon/core"
 	"github.com/ledgerwatch/erigon/core/types"
 	"github.com/ledgerwatch/erigon/params"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/ledgerwatch/erigon/turbo/stages"
 	"github.com/stretchr/testify/require"
 )
@@ -32,7 +33,7 @@ func TestTxPoolContent(t *testing.T) {
 	ctx, conn := rpcdaemontest.CreateTestGrpcConn(t, m)
 	txPool := txpool.NewTxpoolClient(conn)
 	ff := filters.New(ctx, nil, txPool, txpool.NewMiningClient(conn))
-	api := NewTxPoolAPI(NewBaseApi(ff, kvcache.New(kvcache.DefaultCoherentConfig), false), m.DB, txPool)
+	api := NewTxPoolAPI(NewBaseApi(ff, kvcache.New(kvcache.DefaultCoherentConfig), snapshotsync.NewBlockReader(), false), m.DB, txPool)
 
 	expectValue := uint64(1234)
 	txn, err := types.SignTx(types.NewTransaction(0, common.Address{1}, uint256.NewInt(expectValue), params.TxGas, uint256.NewInt(10*params.GWei), nil), *types.LatestSignerForChainID(m.ChainConfig.ChainID), m.Key)
diff --git a/cmd/rpcdaemon/interfaces/interfaces.go b/cmd/rpcdaemon/interfaces/interfaces.go
new file mode 100644
index 0000000000..1a1be7cc3c
--- /dev/null
+++ b/cmd/rpcdaemon/interfaces/interfaces.go
@@ -0,0 +1,13 @@
+package interfaces
+
+import (
+	"context"
+
+	"github.com/ledgerwatch/erigon-lib/kv"
+	"github.com/ledgerwatch/erigon/common"
+	"github.com/ledgerwatch/erigon/core/types"
+)
+
+type BlockReader interface {
+	BlockWithSenders(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (block *types.Block, senders []common.Address, err error)
+}
diff --git a/cmd/rpcdaemon/main.go b/cmd/rpcdaemon/main.go
index a9b525d95f..bb3bab5023 100644
--- a/cmd/rpcdaemon/main.go
+++ b/cmd/rpcdaemon/main.go
@@ -16,7 +16,7 @@ func main() {
 	rootCtx, rootCancel := utils.RootContext()
 	cmd.RunE = func(cmd *cobra.Command, args []string) error {
 		logger := log.New()
-		db, backend, txPool, mining, stateCache, err := cli.RemoteServices(cmd.Context(), *cfg, logger, rootCancel)
+		db, backend, txPool, mining, stateCache, blockReader, err := cli.RemoteServices(cmd.Context(), *cfg, logger, rootCancel)
 		if err != nil {
 			log.Error("Could not connect to DB", "error", err)
 			return nil
@@ -30,7 +30,7 @@ func main() {
 			log.Info("filters are not supported in chaindata mode")
 		}
 
-		if err := cli.StartRpcServer(cmd.Context(), *cfg, commands.APIList(cmd.Context(), db, backend, txPool, mining, ff, stateCache, *cfg, nil)); err != nil {
+		if err := cli.StartRpcServer(cmd.Context(), *cfg, commands.APIList(cmd.Context(), db, backend, txPool, mining, ff, stateCache, blockReader, *cfg, nil)); err != nil {
 			log.Error(err.Error())
 			return nil
 		}
diff --git a/cmd/rpcdaemon/rpcdaemontest/test_util.go b/cmd/rpcdaemon/rpcdaemontest/test_util.go
index 81b7407fc6..fd5054109b 100644
--- a/cmd/rpcdaemon/rpcdaemontest/test_util.go
+++ b/cmd/rpcdaemon/rpcdaemontest/test_util.go
@@ -220,7 +220,7 @@ func CreateTestGrpcConn(t *testing.T, m *stages.MockSentry) (context.Context, *g
 	ethashApi := apis[1].Service.(*ethash.API)
 	server := grpc.NewServer()
 
-	remote.RegisterETHBACKENDServer(server, privateapi.NewEthBackendServer(ctx, nil, m.Notifications.Events))
+	remote.RegisterETHBACKENDServer(server, privateapi.NewEthBackendServer(ctx, nil, m.DB, m.Notifications.Events))
 	txpool.RegisterTxpoolServer(server, m.TxPoolV2GrpcServer)
 	txpool.RegisterMiningServer(server, privateapi.NewMiningServer(ctx, &IsMiningMock{}, ethashApi))
 	listener := bufconn.Listen(1024 * 1024)
diff --git a/cmd/rpcdaemon/services/eth_backend.go b/cmd/rpcdaemon/services/eth_backend.go
index 6b3a45430b..397446a49d 100644
--- a/cmd/rpcdaemon/services/eth_backend.go
+++ b/cmd/rpcdaemon/services/eth_backend.go
@@ -1,6 +1,7 @@
 package services
 
 import (
+	"bytes"
 	"context"
 	"errors"
 	"fmt"
@@ -8,8 +9,11 @@ import (
 
 	"github.com/ledgerwatch/erigon-lib/gointerfaces"
 	"github.com/ledgerwatch/erigon-lib/gointerfaces/remote"
+	"github.com/ledgerwatch/erigon-lib/kv"
 	"github.com/ledgerwatch/erigon/common"
+	"github.com/ledgerwatch/erigon/core/types"
 	"github.com/ledgerwatch/erigon/ethdb/privateapi"
+	"github.com/ledgerwatch/erigon/rlp"
 	"github.com/ledgerwatch/log/v3"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/status"
@@ -26,19 +30,22 @@ type ApiBackend interface {
 	ProtocolVersion(ctx context.Context) (uint64, error)
 	ClientVersion(ctx context.Context) (string, error)
 	Subscribe(ctx context.Context, cb func(*remote.SubscribeReply)) error
+	BlockWithSenders(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (block *types.Block, senders []common.Address, err error)
 }
 
 type RemoteBackend struct {
 	remoteEthBackend remote.ETHBACKENDClient
 	log              log.Logger
 	version          gointerfaces.Version
+	db               kv.RoDB
 }
 
-func NewRemoteBackend(cc grpc.ClientConnInterface) *RemoteBackend {
+func NewRemoteBackend(cc grpc.ClientConnInterface, db kv.RoDB) *RemoteBackend {
 	return &RemoteBackend{
 		remoteEthBackend: remote.NewETHBACKENDClient(cc),
 		version:          gointerfaces.VersionFromProto(privateapi.EthBackendAPIVersion),
 		log:              log.New("remote_service", "eth_backend"),
+		db:               db,
 	}
 }
 
@@ -141,3 +148,23 @@ func (back *RemoteBackend) Subscribe(ctx context.Context, onNewEvent func(*remot
 	}
 	return nil
 }
+
+func (back *RemoteBackend) BlockWithSenders(ctx context.Context, _ kv.Tx, hash common.Hash, blockHeight uint64) (block *types.Block, senders []common.Address, err error) {
+	reply, err := back.remoteEthBackend.Block(ctx, &remote.BlockRequest{BlockHash: gointerfaces.ConvertHashToH256(hash), BlockHeight: blockHeight})
+	if err != nil {
+		return nil, nil, err
+	}
+	block = &types.Block{}
+	err = rlp.Decode(bytes.NewReader(reply.BlockRlp), block)
+	if err != nil {
+		return nil, nil, err
+	}
+	senders = make([]common.Address, len(reply.Senders)/20)
+	for i := range senders {
+		senders[i].SetBytes(reply.Senders[i*20 : (i+1)*20])
+	}
+	if len(senders) == block.Transactions().Len() { //it's fine if no senders provided - they can be lazy recovered
+		block.SendersToTxs(senders)
+	}
+	return block, senders, nil
+}
diff --git a/eth/backend.go b/eth/backend.go
index 367aa21995..67bb28e2be 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -374,7 +374,7 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere
 		ethashApi = casted.APIs(nil)[1].Service.(*ethash.API)
 	}
 
-	ethBackendRPC := privateapi.NewEthBackendServer(ctx, backend, backend.notifications.Events)
+	ethBackendRPC := privateapi.NewEthBackendServer(ctx, backend, backend.chainDB, backend.notifications.Events)
 	miningRPC = privateapi.NewMiningServer(ctx, backend, ethashApi)
 	if stack.Config().PrivateApiAddr != "" {
 		var creds credentials.TransportCredentials
diff --git a/ethdb/privateapi/ethbackend.go b/ethdb/privateapi/ethbackend.go
index c9967323fa..41277d8a44 100644
--- a/ethdb/privateapi/ethbackend.go
+++ b/ethdb/privateapi/ethbackend.go
@@ -7,10 +7,12 @@ import (
 	"github.com/ledgerwatch/erigon-lib/gointerfaces"
 	"github.com/ledgerwatch/erigon-lib/gointerfaces/remote"
 	types2 "github.com/ledgerwatch/erigon-lib/gointerfaces/types"
+	"github.com/ledgerwatch/erigon-lib/kv"
 	"github.com/ledgerwatch/erigon/common"
 	"github.com/ledgerwatch/erigon/core/types"
 	"github.com/ledgerwatch/erigon/params"
 	"github.com/ledgerwatch/erigon/rlp"
+	"github.com/ledgerwatch/erigon/turbo/snapshotsync"
 	"github.com/ledgerwatch/log/v3"
 	"google.golang.org/protobuf/types/known/emptypb"
 )
@@ -23,9 +25,11 @@ var EthBackendAPIVersion = &types2.VersionReply{Major: 2, Minor: 1, Patch: 0}
 type EthBackendServer struct {
 	remote.UnimplementedETHBACKENDServer // must be embedded to have forward compatible implementations.
 
-	ctx    context.Context
-	eth    EthBackend
-	events *Events
+	ctx         context.Context
+	eth         EthBackend
+	events      *Events
+	db          kv.RoDB
+	blockReader *snapshotsync.BlockReader
 }
 
 type EthBackend interface {
@@ -34,8 +38,8 @@ type EthBackend interface {
 	NetPeerCount() (uint64, error)
 }
 
-func NewEthBackendServer(ctx context.Context, eth EthBackend, events *Events) *EthBackendServer {
-	return &EthBackendServer{ctx: ctx, eth: eth, events: events}
+func NewEthBackendServer(ctx context.Context, eth EthBackend, db kv.RoDB, events *Events) *EthBackendServer {
+	return &EthBackendServer{ctx: ctx, eth: eth, events: events, db: db, blockReader: snapshotsync.NewBlockReader()}
 }
 
 func (s *EthBackendServer) Version(context.Context, *emptypb.Empty) (*types2.VersionReply, error) {
@@ -121,3 +125,25 @@ func (s *EthBackendServer) ProtocolVersion(_ context.Context, _ *remote.Protocol
 func (s *EthBackendServer) ClientVersion(_ context.Context, _ *remote.ClientVersionRequest) (*remote.ClientVersionReply, error) {
 	return &remote.ClientVersionReply{NodeName: common.MakeName("erigon", params.Version)}, nil
 }
+
+func (s *EthBackendServer) Block(ctx context.Context, req *remote.BlockRequest) (*remote.BlockReply, error) {
+	tx, err := s.db.BeginRo(ctx)
+	if err != nil {
+		return nil, err
+	}
+	defer tx.Rollback()
+
+	block, senders, err := s.blockReader.BlockWithSenders(ctx, tx, gointerfaces.ConvertH256ToHash(req.BlockHash), req.BlockHeight)
+	if err != nil {
+		return nil, err
+	}
+	blockRlp, err := rlp.EncodeToBytes(block)
+	if err != nil {
+		return nil, err
+	}
+	sendersBytes := make([]byte, 20*len(senders))
+	for i := range senders {
+		sendersBytes = append(sendersBytes, senders[i][:]...)
+	}
+	return &remote.BlockReply{BlockRlp: blockRlp, Senders: sendersBytes}, nil
+}
diff --git a/go.mod b/go.mod
index d5be411418..41e78d3214 100644
--- a/go.mod
+++ b/go.mod
@@ -35,7 +35,7 @@ require (
 	github.com/json-iterator/go v1.1.12
 	github.com/julienschmidt/httprouter v1.3.0
 	github.com/kevinburke/go-bindata v3.21.0+incompatible
-	github.com/ledgerwatch/erigon-lib v0.0.0-20211110145339-ab66a02f5284
+	github.com/ledgerwatch/erigon-lib v0.0.0-20211114031802-6f8b7293f1de
 	github.com/ledgerwatch/log/v3 v3.4.0
 	github.com/ledgerwatch/secp256k1 v1.0.0
 	github.com/logrusorgru/aurora/v3 v3.0.0
@@ -64,4 +64,4 @@ require (
 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
 	gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6
 	pgregory.net/rapid v0.4.7
-)
\ No newline at end of file
+)
diff --git a/go.sum b/go.sum
index 5564c91b02..c86a13b6da 100644
--- a/go.sum
+++ b/go.sum
@@ -596,8 +596,8 @@ github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758 h1:0D5M2HQSGD3P
 github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
 github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
-github.com/ledgerwatch/erigon-lib v0.0.0-20211110145339-ab66a02f5284 h1:YV9tJXU5LB7UaVI6CLoSVxk56MaPQGZoaXjzu7me9Sg=
-github.com/ledgerwatch/erigon-lib v0.0.0-20211110145339-ab66a02f5284/go.mod h1:CuEZROm43MykZT5CjCj02jw0FOwaDl8Nh+PZkTEGopg=
+github.com/ledgerwatch/erigon-lib v0.0.0-20211114031802-6f8b7293f1de h1:zGmryhCgTlnnHBWN1u0m1R6P6NRKf/iszVniVNYXRpU=
+github.com/ledgerwatch/erigon-lib v0.0.0-20211114031802-6f8b7293f1de/go.mod h1:CuEZROm43MykZT5CjCj02jw0FOwaDl8Nh+PZkTEGopg=
 github.com/ledgerwatch/log/v3 v3.4.0 h1:SEIOcv5a2zkG3PmoT5jeTU9m/0nEUv0BJS5bzsjwKCI=
 github.com/ledgerwatch/log/v3 v3.4.0/go.mod h1:VXcz6Ssn6XEeU92dCMc39/g1F0OYAjw1Mt+dGP5DjXY=
 github.com/ledgerwatch/secp256k1 v1.0.0 h1:Usvz87YoTG0uePIV8woOof5cQnLXGYa162rFf3YnwaQ=
@@ -1514,4 +1514,4 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
 sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
-sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
\ No newline at end of file
+sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
diff --git a/rpc/client_test.go b/rpc/client_test.go
index 4a00912987..c3a2e7118d 100644
--- a/rpc/client_test.go
+++ b/rpc/client_test.go
@@ -24,13 +24,13 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"reflect"
-	"runtime"
 	"strings"
 	"sync"
 	"testing"
 	"time"
 
 	"github.com/davecgh/go-spew/spew"
+	"github.com/ledgerwatch/erigon-lib/common/dbg"
 	"github.com/ledgerwatch/log/v3"
 )
 
@@ -259,10 +259,8 @@ func TestClientSubscribeInvalidArg(t *testing.T) {
 			}
 			if !shouldPanic && err != nil {
 				t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg)
-				buf := make([]byte, 1024*1024)
-				buf = buf[:runtime.Stack(buf, false)]
 				t.Error(err)
-				t.Error(string(buf))
+				t.Error(dbg.Stack())
 			}
 		}()
 		client.EthSubscribe(context.Background(), arg, "foo_bar")
diff --git a/rpc/service.go b/rpc/service.go
index b0b0e08f3c..25b01a9e72 100644
--- a/rpc/service.go
+++ b/rpc/service.go
@@ -21,7 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"reflect"
-	"runtime"
+	"runtime/debug"
 	"strings"
 	"sync"
 	"unicode"
@@ -214,12 +214,8 @@ func (c *callback) call(ctx context.Context, method string, args []reflect.Value
 	// Catch panic while running the callback.
 	defer func() {
 		if err := recover(); err != nil {
-			const size = 64 << 10
-			buf := make([]byte, size)
-			buf = buf[:runtime.Stack(buf, false)]
-			log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf))
+			log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, debug.Stack()))
 			errRes = errors.New("method handler crashed")
-			//debug.WriteStackTraceOnPanic(string(buf))
 		}
 	}()
 	// Run the callback.
diff --git a/turbo/adapter/block_getter.go b/turbo/adapter/block_getter.go
deleted file mode 100644
index c92fa56d54..0000000000
--- a/turbo/adapter/block_getter.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package adapter
-
-import (
-	"github.com/ledgerwatch/erigon-lib/kv"
-	"github.com/ledgerwatch/erigon/common"
-	"github.com/ledgerwatch/erigon/core/rawdb"
-	"github.com/ledgerwatch/erigon/core/types"
-)
-
-func NewBlockGetter(tx kv.Tx) *blockGetter {
-	return &blockGetter{tx}
-}
-
-type blockGetter struct {
-	tx kv.Tx
-}
-
-func (g *blockGetter) GetBlockByHash(hash common.Hash) (*types.Block, error) {
-	return rawdb.ReadBlockByHash(g.tx, hash)
-}
-
-func (g *blockGetter) GetBlock(hash common.Hash, number uint64) *types.Block {
-	return rawdb.ReadBlock(g.tx, hash, number)
-}
diff --git a/turbo/adapter/chain_context.go b/turbo/adapter/chain_context.go
deleted file mode 100644
index 8224efd258..0000000000
--- a/turbo/adapter/chain_context.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package adapter
-
-import (
-	"math/big"
-
-	"github.com/ledgerwatch/erigon-lib/kv"
-	"github.com/ledgerwatch/erigon/common"
-	"github.com/ledgerwatch/erigon/consensus"
-	"github.com/ledgerwatch/erigon/core/rawdb"
-	"github.com/ledgerwatch/erigon/core/state"
-	"github.com/ledgerwatch/erigon/core/types"
-	"github.com/ledgerwatch/erigon/params"
-	"github.com/ledgerwatch/erigon/rlp"
-	"github.com/ledgerwatch/erigon/rpc"
-)
-
-type chainContext struct {
-	tx kv.Tx
-}
-
-func NewChainContext(tx kv.Tx) *chainContext {
-	return &chainContext{
-		tx: tx,
-	}
-}
-
-type powEngine struct {
-}
-
-func (c *powEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
-
-	panic("must not be called")
-}
-func (c *powEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) error {
-	panic("must not be called")
-}
-func (c *powEngine) VerifyUncles(chain consensus.ChainReader, block *types.Header, uncles []*types.Header) error {
-	panic("must not be called")
-}
-func (c *powEngine) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error {
-	panic("must not be called")
-}
-func (c *powEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
-	panic("must not be called")
-}
-func (c *powEngine) Initialize(config *params.ChainConfig, chain consensus.ChainHeaderReader, e consensus.EpochReader, header *types.Header, txs []types.Transaction, uncles []*types.Header, syscall consensus.SystemCall) {
-	panic("must not be called")
-}
-func (c *powEngine) Finalize(config *params.ChainConfig, header *types.Header, state *state.IntraBlockState, txs []types.Transaction, uncles []*types.Header, r types.Receipts, e consensus.EpochReader, chain consensus.ChainHeaderReader, syscall consensus.SystemCall) error {
-	panic("must not be called")
-}
-func (c *powEngine) FinalizeAndAssemble(chainConfig *params.ChainConfig, header *types.Header, state *state.IntraBlockState,
-	txs []types.Transaction, uncles []*types.Header, receipts types.Receipts,
-	e consensus.EpochReader, h consensus.ChainHeaderReader, syscall consensus.SystemCall, call consensus.Call) (*types.Block, error) {
-	panic("must not be called")
-}
-
-func (c *powEngine) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
-	panic("must not be called")
-}
-func (c *powEngine) SealHash(header *types.Header) common.Hash {
-	panic("must not be called")
-}
-
-func (c *powEngine) GenerateSeal(chain consensus.ChainHeaderReader, currnt, parent *types.Header, call consensus.Call) []rlp.RawValue {
-	return nil
-}
-
-func (c *powEngine) CalcDifficulty(chain consensus.ChainHeaderReader, time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, parentHash, parentUncleHash common.Hash, _ []rlp.RawValue) *big.Int {
-	panic("must not be called")
-}
-func (c *powEngine) APIs(chain consensus.ChainHeaderReader) []rpc.API {
-	panic("must not be called")
-}
-
-func (c *powEngine) Close() error {
-	panic("must not be called")
-}
-
-func (c *powEngine) Author(header *types.Header) (common.Address, error) {
-	return header.Coinbase, nil
-}
-
-func (c *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
-	return rawdb.ReadHeader(c.tx, hash, number)
-}
-
-func (c *chainContext) Engine() consensus.Engine {
-	return &powEngine{}
-}
diff --git a/turbo/snapshotsync/block_reader.go b/turbo/snapshotsync/block_reader.go
new file mode 100644
index 0000000000..83f2efacfd
--- /dev/null
+++ b/turbo/snapshotsync/block_reader.go
@@ -0,0 +1,27 @@
+package snapshotsync
+
+import (
+	"context"
+
+	"github.com/ledgerwatch/erigon-lib/kv"
+	"github.com/ledgerwatch/erigon/common"
+	"github.com/ledgerwatch/erigon/core/rawdb"
+	"github.com/ledgerwatch/erigon/core/types"
+)
+
+// BlockReader can read blocks from db and snapshots
+type BlockReader struct {
+}
+
+func NewBlockReader() *BlockReader {
+	return &BlockReader{}
+}
+
+func (back *BlockReader) BlockWithSenders(ctx context.Context, tx kv.Tx, hash common.Hash, blockHeight uint64) (block *types.Block, senders []common.Address, err error) {
+	block, senders, err = rawdb.ReadBlockWithSenders(tx, hash, blockHeight)
+	if err != nil {
+		return nil, nil, err
+	}
+	//TODO: read snapshots
+	return block, senders, nil
+}
-- 
GitLab