From b0bb30c50565f2fee9fc9046504923573bb684f4 Mon Sep 17 00:00:00 2001
From: Marius van der Wijden <m.vanderwijden@live.de>
Date: Fri, 24 Jun 2022 13:55:40 +0200
Subject: [PATCH 1/2] core: eth: rpc: implement safe rpc block

---
 core/blockchain.go         | 15 ++++++++++++++-
 core/blockchain_reader.go  |  6 ++++++
 eth/api.go                 |  4 ++++
 eth/api_backend.go         |  6 ++++++
 eth/catalyst/api.go        |  2 ++
 internal/jsre/deps/web3.js |  2 +-
 rpc/types.go               | 10 ++++++++++
 7 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/core/blockchain.go b/core/blockchain.go
index fbeddecdbb29..2e54911e2623 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -51,6 +51,7 @@ var (
 	headHeaderGauge         = metrics.NewRegisteredGauge("chain/head/header", nil)
 	headFastBlockGauge      = metrics.NewRegisteredGauge("chain/head/receipt", nil)
 	headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil)
+	headSafeBlockGauge      = metrics.NewRegisteredGauge("chain/head/safe", nil)
 
 	accountReadTimer   = metrics.NewRegisteredTimer("chain/account/reads", nil)
 	accountHashTimer   = metrics.NewRegisteredTimer("chain/account/hashes", nil)
@@ -191,6 +192,7 @@ type BlockChain struct {
 	currentBlock          atomic.Value // Current head of the block chain
 	currentFastBlock      atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
 	currentFinalizedBlock atomic.Value // Current finalized head
+	currentSafeBlock      atomic.Value // Current safe head
 
 	stateCache    state.Database // State database to reuse between imports (contains state cache)
 	bodyCache     *lru.Cache     // Cache for the most recent block bodies
@@ -267,6 +269,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
 	bc.currentBlock.Store(nilBlock)
 	bc.currentFastBlock.Store(nilBlock)
 	bc.currentFinalizedBlock.Store(nilBlock)
+	bc.currentSafeBlock.Store(nilBlock)
 
 	// Initialize the chain with ancient data if it isn't empty.
 	var txIndexBlock uint64
@@ -464,11 +467,15 @@ func (bc *BlockChain) loadLastState() error {
 		}
 	}
 
-	// Restore the last known finalized block
+	// Restore the last known finalized block amd safe block
+	// Note: the safe block is not stored on disk and it is set to the last
+	// known finalized block on startup
 	if head := rawdb.ReadFinalizedBlockHash(bc.db); head != (common.Hash{}) {
 		if block := bc.GetBlockByHash(head); block != nil {
 			bc.currentFinalizedBlock.Store(block)
 			headFinalizedBlockGauge.Update(int64(block.NumberU64()))
+			bc.currentSafeBlock.Store(block)
+			headSafeBlockGauge.Update(int64(block.NumberU64()))
 		}
 	}
 	// Issue a status log for the user
@@ -508,6 +515,12 @@ func (bc *BlockChain) SetFinalized(block *types.Block) {
 	headFinalizedBlockGauge.Update(int64(block.NumberU64()))
 }
 
+// SetSafe sets the safe block.
+func (bc *BlockChain) SetSafe(block *types.Block) {
+	bc.currentSafeBlock.Store(block)
+	headSafeBlockGauge.Update(int64(block.NumberU64()))
+}
+
 // setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
 // that the rewind must pass the specified state root. This method is meant to be
 // used when rewinding with snapshots enabled to ensure that we go back further than
diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go
index b8d4233c6ecd..96e9f80b6aac 100644
--- a/core/blockchain_reader.go
+++ b/core/blockchain_reader.go
@@ -55,6 +55,12 @@ func (bc *BlockChain) CurrentFinalizedBlock() *types.Block {
 	return bc.currentFinalizedBlock.Load().(*types.Block)
 }
 
+// CurrentSafeBlock retrieves the current safe block of the canonical
+// chain. The block is retrieved from the blockchain's internal cache.
+func (bc *BlockChain) CurrentSafeBlock() *types.Block {
+	return bc.currentSafeBlock.Load().(*types.Block)
+}
+
 // HasHeader checks if a block header is present in the database or not, caching
 // it if present.
 func (bc *BlockChain) HasHeader(hash common.Hash, number uint64) bool {
diff --git a/eth/api.go b/eth/api.go
index 5d159108e6a9..5dc0d2eaa8c7 100644
--- a/eth/api.go
+++ b/eth/api.go
@@ -272,6 +272,8 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) {
 		block = api.eth.blockchain.CurrentBlock()
 	} else if blockNr == rpc.FinalizedBlockNumber {
 		block = api.eth.blockchain.CurrentFinalizedBlock()
+	} else if blockNr == rpc.SafeBlockNumber {
+		block = api.eth.blockchain.CurrentSafeBlock()
 	} else {
 		block = api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
 	}
@@ -350,6 +352,8 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
 				block = api.eth.blockchain.CurrentBlock()
 			} else if number == rpc.FinalizedBlockNumber {
 				block = api.eth.blockchain.CurrentFinalizedBlock()
+			} else if number == rpc.SafeBlockNumber {
+				block = api.eth.blockchain.CurrentSafeBlock()
 			} else {
 				block = api.eth.blockchain.GetBlockByNumber(uint64(number))
 			}
diff --git a/eth/api_backend.go b/eth/api_backend.go
index f942710e2d8d..9c26dfd93411 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -76,6 +76,9 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
 	if number == rpc.FinalizedBlockNumber {
 		return b.eth.blockchain.CurrentFinalizedBlock().Header(), nil
 	}
+	if number == rpc.SafeBlockNumber {
+		return b.eth.blockchain.CurrentSafeBlock().Header(), nil
+	}
 	return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
 }
 
@@ -113,6 +116,9 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
 	if number == rpc.FinalizedBlockNumber {
 		return b.eth.blockchain.CurrentFinalizedBlock(), nil
 	}
+	if number == rpc.SafeBlockNumber {
+		return b.eth.blockchain.CurrentSafeBlock(), nil
+	}
 	return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
 }
 
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index feccff881e31..b969d7be0b61 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -191,6 +191,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
 			log.Warn("Safe block not in canonical chain")
 			return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain"))
 		}
+		// Set the safe block
+		api.eth.BlockChain().SetSafe(safeBlock)
 	}
 	// If payload generation was requested, create a new block to be potentially
 	// sealed by the beacon client. The payload will be requested later, and we
diff --git a/internal/jsre/deps/web3.js b/internal/jsre/deps/web3.js
index f82d93bdc570..a291218ec51f 100644
--- a/internal/jsre/deps/web3.js
+++ b/internal/jsre/deps/web3.js
@@ -3696,7 +3696,7 @@ var outputBigNumberFormatter = function (number) {
 };
 
 var isPredefinedBlockNumber = function (blockNumber) {
-    return blockNumber === 'latest' || blockNumber === 'pending' || blockNumber === 'earliest' || blockNumber === 'finalized';
+    return blockNumber === 'latest' || blockNumber === 'pending' || blockNumber === 'earliest' || blockNumber === 'finalized' || blockNumber === 'safe';
 };
 
 var inputDefaultBlockNumberFormatter = function (blockNumber) {
diff --git a/rpc/types.go b/rpc/types.go
index 369e950aa162..e3d1a4896821 100644
--- a/rpc/types.go
+++ b/rpc/types.go
@@ -61,6 +61,7 @@ type jsonWriter interface {
 type BlockNumber int64
 
 const (
+	SafeBlockNumber      = BlockNumber(-4)
 	FinalizedBlockNumber = BlockNumber(-3)
 	PendingBlockNumber   = BlockNumber(-2)
 	LatestBlockNumber    = BlockNumber(-1)
@@ -92,6 +93,9 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
 	case "finalized":
 		*bn = FinalizedBlockNumber
 		return nil
+	case "safe":
+		*bn = SafeBlockNumber
+		return nil
 	}
 
 	blckNum, err := hexutil.DecodeUint64(input)
@@ -118,6 +122,8 @@ func (bn BlockNumber) MarshalText() ([]byte, error) {
 		return []byte("pending"), nil
 	case FinalizedBlockNumber:
 		return []byte("finalized"), nil
+	case SafeBlockNumber:
+		return []byte("safe"), nil
 	default:
 		return hexutil.Uint64(bn).MarshalText()
 	}
@@ -168,6 +174,10 @@ func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
 		bn := FinalizedBlockNumber
 		bnh.BlockNumber = &bn
 		return nil
+	case "safe":
+		bn := SafeBlockNumber
+		bnh.BlockNumber = &bn
+		return nil
 	default:
 		if len(input) == 66 {
 			hash := common.Hash{}

From 1e924f2f064a3d11aa15fdc2f63080842a8ced5c Mon Sep 17 00:00:00 2001
From: Marius van der Wijden <m.vanderwijden@live.de>
Date: Thu, 14 Jul 2022 14:27:15 +0200
Subject: [PATCH 2/2] core: fix setHead, panics

---
 core/blockchain.go | 27 +++++++++++++++++++++++----
 eth/api_backend.go | 12 ++++++++++--
 2 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/core/blockchain.go b/core/blockchain.go
index 2e54911e2623..506034b539a7 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -467,7 +467,7 @@ func (bc *BlockChain) loadLastState() error {
 		}
 	}
 
-	// Restore the last known finalized block amd safe block
+	// Restore the last known finalized block and safe block
 	// Note: the safe block is not stored on disk and it is set to the last
 	// known finalized block on startup
 	if head := rawdb.ReadFinalizedBlockHash(bc.db); head != (common.Hash{}) {
@@ -511,14 +511,23 @@ func (bc *BlockChain) SetHead(head uint64) error {
 // SetFinalized sets the finalized block.
 func (bc *BlockChain) SetFinalized(block *types.Block) {
 	bc.currentFinalizedBlock.Store(block)
-	rawdb.WriteFinalizedBlockHash(bc.db, block.Hash())
-	headFinalizedBlockGauge.Update(int64(block.NumberU64()))
+	if block != nil {
+		rawdb.WriteFinalizedBlockHash(bc.db, block.Hash())
+		headFinalizedBlockGauge.Update(int64(block.NumberU64()))
+	} else {
+		rawdb.WriteFinalizedBlockHash(bc.db, common.Hash{})
+		headFinalizedBlockGauge.Update(0)
+	}
 }
 
 // SetSafe sets the safe block.
 func (bc *BlockChain) SetSafe(block *types.Block) {
 	bc.currentSafeBlock.Store(block)
-	headSafeBlockGauge.Update(int64(block.NumberU64()))
+	if block != nil {
+		headSafeBlockGauge.Update(int64(block.NumberU64()))
+	} else {
+		headSafeBlockGauge.Update(0)
+	}
 }
 
 // setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
@@ -676,6 +685,16 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
 	bc.txLookupCache.Purge()
 	bc.futureBlocks.Purge()
 
+	// Clear safe block, finalized block if needed
+	if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.NumberU64() {
+		log.Warn("SetHead invalidated safe block")
+		bc.SetSafe(nil)
+	}
+	if finalized := bc.CurrentFinalizedBlock(); finalized != nil && head < finalized.NumberU64() {
+		log.Error("SetHead invalidated finalized block")
+		bc.SetFinalized(nil)
+	}
+
 	return rootNumber, bc.loadLastState()
 }
 
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 9c26dfd93411..1d8ba8ea5cae 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -74,10 +74,18 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
 		return b.eth.blockchain.CurrentBlock().Header(), nil
 	}
 	if number == rpc.FinalizedBlockNumber {
-		return b.eth.blockchain.CurrentFinalizedBlock().Header(), nil
+		block := b.eth.blockchain.CurrentFinalizedBlock()
+		if block != nil {
+			return block.Header(), nil
+		}
+		return nil, errors.New("finalized block not found")
 	}
 	if number == rpc.SafeBlockNumber {
-		return b.eth.blockchain.CurrentSafeBlock().Header(), nil
+		block := b.eth.blockchain.CurrentSafeBlock()
+		if block != nil {
+			return block.Header(), nil
+		}
+		return nil, errors.New("safe block not found")
 	}
 	return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
 }