diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 73816e833c9654710d697328d148156cd29f9b81..e5a49429c979637937983b3dfb26981cf2116f7b 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -109,6 +109,7 @@ var (
 		utils.CacheTrieFlag,
 		utils.CacheGCFlag,
 		utils.TrieCacheGenFlag,
+		utils.DownloadOnlyFlag,
 		utils.NoHistory,
 		utils.ArchiveSyncInterval,
 		utils.ListenPortFlag,
diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go
index 58d3fcdcf607cba50527755d461bd322a1d0040f..b79007678dc6e0eea52d35d963aa9f583d61d4cd 100644
--- a/cmd/geth/usage.go
+++ b/cmd/geth/usage.go
@@ -87,6 +87,7 @@ var AppHelpFlagGroups = []flagGroup{
 			utils.IdentityFlag,
 			utils.LightKDFFlag,
 			utils.WhitelistFlag,
+			utils.DownloadOnlyFlag,
 			utils.NoHistory,
 			utils.ArchiveSyncInterval,
 		},
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 12b71742a5d8f5717a5cfacef1e43d10a80ad4b6..e86601e90c552662459d6535c5fa8ffefa6fbb8a 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -297,6 +297,10 @@ var (
 		Name:  "ulc.onlyannounce",
 		Usage: "Ultra light server sends announcements only",
 	}
+	DownloadOnlyFlag = cli.BoolFlag{
+		Name:  "download-only",
+		Usage: "Run in download only mode - only fetch blocks but not process them",
+	}
 	// Dashboard settings
 	DashboardEnabledFlag = cli.BoolFlag{
 		Name:  "dashboard",
@@ -1468,6 +1472,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
 	cfg.BlocksToPrune = ctx.GlobalUint64(GCModeBlockToPruneFlag.Name)
 	cfg.PruningTimeout = ctx.GlobalDuration(GCModeTickTimeout.Name)
 
+	cfg.DownloadOnly = ctx.GlobalBoolT(DownloadOnlyFlag.Name)
 	cfg.NoHistory = ctx.GlobalBoolT(NoHistory.Name)
 	cfg.ArchiveSyncInterval = ctx.GlobalInt(ArchiveSyncInterval.Name)
 
diff --git a/core/blockchain.go b/core/blockchain.go
index 1b3499994c42ffedf1f0562e97234577150f434f..e0ffca15871934c74b3be187c56e3f7bc5d7e035 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -124,6 +124,7 @@ type CacheConfig struct {
 	BlocksToPrune       uint64
 	PruneTimeout        time.Duration
 	ArchiveSyncInterval uint64
+	DownloadOnly        bool
 	NoHistory           bool
 }
 
@@ -205,6 +206,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
 			TrieCleanLimit:      256,
 			TrieDirtyLimit:      256,
 			TrieTimeLimit:       5 * time.Minute,
+			DownloadOnly:        false,
 			NoHistory:           false,
 		}
 	}
@@ -1243,7 +1245,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
 
 // writeBlockWithState writes the block and all associated state to the database,
 // but is expects the chain mutex to be held.
-func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.IntraBlockState, tds *state.TrieDbState) (status WriteStatus, err error) {
+func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, stateDb *state.IntraBlockState, tds *state.TrieDbState) (status WriteStatus, err error) {
 	bc.wg.Add(1)
 	defer bc.wg.Done()
 
@@ -1264,13 +1266,17 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
 	}
 	rawdb.WriteBlock(bc.db, block)
 
-	tds.SetBlockNr(block.NumberU64())
+	if tds != nil {
+		tds.SetBlockNr(block.NumberU64())
+	}
 
 	ctx := bc.WithContext(context.Background(), block.Number())
-	if err := state.CommitBlock(ctx, tds.DbStateWriter()); err != nil {
-		return NonStatTy, err
+	if stateDb != nil {
+		if err := stateDb.CommitBlock(ctx, tds.DbStateWriter()); err != nil {
+			return NonStatTy, err
+		}
 	}
-	if bc.enableReceipts {
+	if bc.enableReceipts && !bc.cacheConfig.DownloadOnly {
 		rawdb.WriteReceipts(bc.db, block.Hash(), block.NumberU64(), receipts)
 	}
 
@@ -1300,8 +1306,12 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
 		}
 	}
 	// Write the positional metadata for transaction/receipt lookups and preimages
-	rawdb.WriteTxLookupEntries(bc.db, block)
-	rawdb.WritePreimages(bc.db, state.Preimages())
+	if !bc.cacheConfig.DownloadOnly {
+		rawdb.WriteTxLookupEntries(bc.db, block)
+	}
+	if stateDb != nil && !bc.cacheConfig.DownloadOnly {
+		rawdb.WritePreimages(bc.db, stateDb.Preimages())
+	}
 
 	status = CanonStatTy
 	//} else {
@@ -1547,17 +1557,19 @@ func (bc *BlockChain) insertChain(ctx context.Context, chain types.Blocks, verif
 		}
 		readBlockNr := parentNumber
 		var root common.Hash
-		if bc.trieDbState == nil {
+		if bc.trieDbState == nil && !bc.cacheConfig.DownloadOnly {
 			if _, err = bc.GetTrieDbState(); err != nil {
 				return k, events, coalescedLogs, err
 			}
 		}
-		root = bc.trieDbState.LastRoot()
+		if !bc.cacheConfig.DownloadOnly {
+			root = bc.trieDbState.LastRoot()
+		}
 		var parentRoot common.Hash
 		if parent != nil {
 			parentRoot = parent.Root()
 		}
-		if parent != nil && root != parentRoot {
+		if parent != nil && root != parentRoot && !bc.cacheConfig.DownloadOnly {
 			log.Info("Rewinding from", "block", bc.CurrentBlock().NumberU64(), "to block", readBlockNr)
 			if _, err = bc.db.Commit(); err != nil {
 				log.Error("Could not commit chainDb before rewinding", "error", err)
@@ -1591,38 +1603,44 @@ func (bc *BlockChain) insertChain(ctx context.Context, chain types.Blocks, verif
 				return 0, events, coalescedLogs, err
 			}
 		}
-		stateDB := state.New(bc.trieDbState)
-		// Process block using the parent state as reference point.
-		//t0 := time.Now()
-		receipts, logs, usedGas, err := bc.processor.Process(block, stateDB, bc.trieDbState, bc.vmConfig)
-		//t1 := time.Now()
-		if err != nil {
-			bc.db.Rollback()
-			bc.trieDbState = nil
-			bc.reportBlock(block, receipts, err)
-			return k, events, coalescedLogs, err
-		}
-		// Update the metrics touched during block processing
-		/*
-			accountReadTimer.Update(statedb.AccountReads)     // Account reads are complete, we can mark them
-			storageReadTimer.Update(statedb.StorageReads)     // Storage reads are complete, we can mark them
-			accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete, we can mark them
-			storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them
-
-			triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation
-			trieproc := statedb.AccountReads + statedb.AccountUpdates
-			trieproc += statedb.StorageReads + statedb.StorageUpdates
-
-			blockExecutionTimer.Update(time.Since(substart) - trieproc - triehash)
-		*/
-
-		// Validate the state using the default validator
-		err = bc.Validator().ValidateState(block, parent, stateDB, bc.trieDbState, receipts, usedGas)
-		if err != nil {
-			bc.db.Rollback()
-			bc.trieDbState = nil
-			bc.reportBlock(block, receipts, err)
-			return k, events, coalescedLogs, err
+		var stateDB *state.IntraBlockState
+		var receipts types.Receipts
+		var logs []*types.Log
+		var usedGas uint64
+		if !bc.cacheConfig.DownloadOnly {
+			stateDB = state.New(bc.trieDbState)
+			// Process block using the parent state as reference point.
+			//t0 := time.Now()
+			receipts, logs, usedGas, err = bc.processor.Process(block, stateDB, bc.trieDbState, bc.vmConfig)
+			//t1 := time.Now()
+			if err != nil {
+				bc.db.Rollback()
+				bc.trieDbState = nil
+				bc.reportBlock(block, receipts, err)
+				return k, events, coalescedLogs, err
+			}
+			// Update the metrics touched during block processing
+			/*
+				accountReadTimer.Update(statedb.AccountReads)     // Account reads are complete, we can mark them
+				storageReadTimer.Update(statedb.StorageReads)     // Storage reads are complete, we can mark them
+				accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete, we can mark them
+				storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them
+
+				triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation
+				trieproc := statedb.AccountReads + statedb.AccountUpdates
+				trieproc += statedb.StorageReads + statedb.StorageUpdates
+
+				blockExecutionTimer.Update(time.Since(substart) - trieproc - triehash)
+			*/
+
+			// Validate the state using the default validator
+			err = bc.Validator().ValidateState(block, parent, stateDB, bc.trieDbState, receipts, usedGas)
+			if err != nil {
+				bc.db.Rollback()
+				bc.trieDbState = nil
+				bc.reportBlock(block, receipts, err)
+				return k, events, coalescedLogs, err
+			}
 		}
 		proctime := time.Since(start)
 
@@ -1693,7 +1711,9 @@ func (bc *BlockChain) insertChain(ctx context.Context, chain types.Blocks, verif
 				bc.trieDbState = nil
 				return 0, events, coalescedLogs, err
 			}
-			bc.trieDbState.PruneTries(false)
+			if bc.trieDbState != nil {
+				bc.trieDbState.PruneTries(false)
+			}
 			log.Info("Database", "size", bc.db.Size(), "written", written)
 		}
 	}
diff --git a/eth/backend.go b/eth/backend.go
index 1d464b63731433f1a7158ec5387f53ef26525ffa..06d445d56c67288cfb5849d2730ed15ba82b91f9 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -186,6 +186,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
 			TrieDirtyLimit:      config.TrieDirtyCache,
 			TrieCleanNoPrefetch: config.NoPrefetch,
 			TrieTimeLimit:       config.TrieTimeout,
+			DownloadOnly:        config.DownloadOnly,
 			NoHistory:           config.NoHistory,
 			ArchiveSyncInterval: uint64(config.ArchiveSyncInterval),
 		}
diff --git a/eth/config.go b/eth/config.go
index 71f829ac5959943ed41c5144fc993bb11a9284ae..024367e513bed3d2031bb5bb075190f140fc404d 100644
--- a/eth/config.go
+++ b/eth/config.go
@@ -98,7 +98,10 @@ type Config struct {
 	NoPruning  bool // Whether to disable pruning and flush everything to disk
 	NoPrefetch bool // Whether to disable prefetching and only load state on demand
 
-	NoHistory           bool
+	NoHistory bool
+	// DownloadOnly is set when the node does not need to process the blocks, but simply
+	// download them
+	DownloadOnly        bool
 	ArchiveSyncInterval int
 	BlocksBeforePruning uint64
 	BlocksToPrune       uint64