Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 14 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ 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!)

currentSafeBlock atomic.Pointer[types.Header] // Current safe block (derived from L1 batch committed)
currentFinalizedBlock atomic.Pointer[types.Header] // Current finalized block (derived from L1 batch finalized)

stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
Expand Down Expand Up @@ -478,6 +481,14 @@ func (bc *BlockChain) loadLastState() error {
headFastBlockGauge.Update(int64(block.NumberU64()))
}
}
// Restore the last known finalized block, and set safe = finalized on startup
if head := rawdb.ReadFinalizedBlockHash(bc.db); head != (common.Hash{}) {
if header := bc.GetHeaderByHash(head); header != nil {
bc.currentFinalizedBlock.Store(header)
bc.currentSafeBlock.Store(header) // safe = finalized on startup
log.Info("Loaded finalized block", "number", header.Number, "hash", header.Hash())
}
}
// Issue a status log for the user
currentFastBlock := bc.CurrentFastBlock()

Expand All @@ -488,6 +499,9 @@ func (bc *BlockChain) loadLastState() error {
log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(currentHeader.Time), 0)))
log.Info("Loaded most recent local full block", "number", currentBlock.Number(), "hash", currentBlock.Hash(), "td", blockTd, "age", common.PrettyAge(time.Unix(int64(currentBlock.Time()), 0)))
log.Info("Loaded most recent local fast block", "number", currentFastBlock.Number(), "hash", currentFastBlock.Hash(), "td", fastTd, "age", common.PrettyAge(time.Unix(int64(currentFastBlock.Time()), 0)))
if finalizedBlock := bc.CurrentFinalizedBlock(); finalizedBlock != nil {
log.Info("Loaded most recent local finalized block", "number", finalizedBlock.Number, "hash", finalizedBlock.Hash())
}
Comment on lines +502 to +504
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the structure of core/blockchain.go and locate setHeadBeyondRoot
wc -l core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 87


🏁 Script executed:

# Search for setHeadBeyondRoot and SetHead implementations
rg -n "func.*setHeadBeyondRoot|func.*SetHead" core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 224


🏁 Script executed:

# Check currentFinalizedBlock and currentSafeBlock usages throughout the codebase
rg -n "currentFinalizedBlock|currentSafeBlock" --type go core/

Repository: morph-l2/go-ethereum

Length of output: 746


🏁 Script executed:

# Read the SetHead function first
sed -n '514,526p' core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 718


🏁 Script executed:

# Read setHeadBeyondRoot implementation - first part
sed -n '527,570p' core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 2322


🏁 Script executed:

# Read more of setHeadBeyondRoot
sed -n '570,620p' core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 2462


🏁 Script executed:

# Read the rest of setHeadBeyondRoot
sed -n '620,662p' core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 1743


🏁 Script executed:

# Search for loadLastState implementation
rg -n "func.*loadLastState" core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 113


🏁 Script executed:

# Read the loadLastState function
sed -n '481,510p' core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 1768


🏁 Script executed:

# Search for GetHeaderByHash implementation
rg -n "func.*GetHeaderByHash" core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 46


🏁 Script executed:

# Read the GetHeaderByHash function
sed -n '246,260p' core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 585


🏁 Script executed:

# Search for GetHeaderByHash more broadly
rg -n "GetHeaderByHash" --type go core/

Repository: morph-l2/go-ethereum

Length of output: 1741


🏁 Script executed:

# Get the line numbers for GetHeaderByHash in blockchain_reader.go
rg -n "func.*GetHeaderByHash" --type go core/blockchain_reader.go

Repository: morph-l2/go-ethereum

Length of output: 138


🏁 Script executed:

# Read the GetHeaderByHash function from blockchain_reader.go
sed -n '89,110p' core/blockchain_reader.go

Repository: morph-l2/go-ethereum

Length of output: 655


🏁 Script executed:

# Read the actual GetHeaderByHash implementation in headerchain.go
sed -n '469,480p' core/headerchain.go

Repository: morph-l2/go-ethereum

Length of output: 490


🏁 Script executed:

# Check what comes after GetHeaderByHash in headerchain.go to see full implementation
sed -n '469,500p' core/headerchain.go

Repository: morph-l2/go-ethereum

Length of output: 1209


🏁 Script executed:

# Check if SetHead or setHeadBeyondRoot updates the finalized block hash in database
rg -n "WriteFinalizedBlockHash|WriteFinalized" --type go core/

Repository: morph-l2/go-ethereum

Length of output: 359


🏁 Script executed:

# Check what WriteFinalizedBlockHash does and where it's called
rg -n "WriteFinalizedBlockHash\|WriteSafeBlockHash" --type go

Repository: morph-l2/go-ethereum

Length of output: 46


🏁 Script executed:

# Check where WriteFinalizedBlockHash is defined and what it does
sed -n '227,235p' core/rawdb/accessors_chain.go

Repository: morph-l2/go-ethereum

Length of output: 484


🏁 Script executed:

# Check if the finalized block header would be deleted during SetHead chain rewind
# Look at the delFn function more closely - what gets deleted?
sed -n '650,670p' core/blockchain.go

Repository: morph-l2/go-ethereum

Length of output: 771


🏁 Script executed:

# Check how blockchain_l2.go updates finalized blocks
sed -n '255,275p' core/blockchain_l2.go

Repository: morph-l2/go-ethereum

Length of output: 714


Clear safe/finalized blocks during chain rewind to prevent stale references.

When setHeadBeyondRoot rewinds the chain below the current finalized block, the finalized block header gets deleted from the database. However, loadLastState() attempts to reload it via GetHeaderByHash(), which returns nil if the header is no longer in the canonical chain. In this case, currentFinalizedBlock is not explicitly cleared and remains pointing to a stale block that is no longer part of the canonical chain. Add logic to clear or validate currentFinalizedBlock and currentSafeBlock when GetHeaderByHash() fails during chain rewind recovery.

🤖 Prompt for AI Agents
In `@core/blockchain.go` around lines 502 - 504, When recovering from a chain
rewind in setHeadBeyondRoot/loadLastState, ensure any finalized or safe block
references are validated and cleared if their headers are missing: after calling
GetHeaderByHash() in loadLastState (or the rewind recovery path), check for a
nil return and if nil set currentFinalizedBlock and/or currentSafeBlock to nil
(or otherwise invalidate them) and update any dependent state; also add the same
validation in setHeadBeyondRoot where headers may be deleted so you don't retain
stale pointers to removed headers.

if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
log.Info("Loaded last fast-sync pivot marker", "number", *pivot)
}
Expand Down
19 changes: 19 additions & 0 deletions core/blockchain_l2.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,22 @@ func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log {
}
return logs
}

// SetSafe sets the safe block header. The safe block is derived from
// L1 batch committed status. It represents the block that has been
// submitted to L1 and is considered safe.
func (bc *BlockChain) SetSafe(header *types.Header) {
bc.currentSafeBlock.Store(header)
log.Debug("Set safe block", "number", header.Number, "hash", header.Hash())
}
Comment on lines +254 to +257
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add nil check to prevent potential panic.

If header is nil, the log.Debug call will panic when accessing header.Number and header.Hash(). Consider adding a nil guard.

🛡️ Suggested fix
 func (bc *BlockChain) SetSafe(header *types.Header) {
+	if header == nil {
+		return
+	}
 	bc.currentSafeBlock.Store(header)
 	log.Debug("Set safe block", "number", header.Number, "hash", header.Hash())
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (bc *BlockChain) SetSafe(header *types.Header) {
bc.currentSafeBlock.Store(header)
log.Debug("Set safe block", "number", header.Number, "hash", header.Hash())
}
func (bc *BlockChain) SetSafe(header *types.Header) {
if header == nil {
return
}
bc.currentSafeBlock.Store(header)
log.Debug("Set safe block", "number", header.Number, "hash", header.Hash())
}
🤖 Prompt for AI Agents
In `@core/blockchain_l2.go` around lines 254 - 257, The SetSafe method can panic
if header is nil because it dereferences header.Number and header.Hash(); add a
nil guard at the start of BlockChain.SetSafe(header *types.Header) that returns
early (or logs a warning) when header == nil, and only call
bc.currentSafeBlock.Store(header) and log.Debug(...) when header is non-nil (use
the function name SetSafe and variable header to locate the change).


// SetFinalized sets the finalized block header. The finalized block is
// derived from L1 batch finalized status. It represents the block that
// has been finalized on L1 and is considered irreversible.
// Also persists to rawdb for recovery on restart.
func (bc *BlockChain) SetFinalized(header *types.Header) {
bc.currentFinalizedBlock.Store(header)
// Persist to rawdb for recovery on restart
rawdb.WriteFinalizedBlockHash(bc.db, header.Hash())
log.Debug("Set finalized block", "number", header.Number, "hash", header.Hash())
}
Comment on lines +263 to +268
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add nil check to prevent potential panic.

Similar to SetSafe, if header is nil, the code will panic when accessing header.Hash() and header.Number in the log statement. The WriteFinalizedBlockHash call would also write a zero hash, which might cause issues on restart recovery.

🛡️ Suggested fix
 func (bc *BlockChain) SetFinalized(header *types.Header) {
+	if header == nil {
+		return
+	}
 	bc.currentFinalizedBlock.Store(header)
 	// Persist to rawdb for recovery on restart
 	rawdb.WriteFinalizedBlockHash(bc.db, header.Hash())
 	log.Debug("Set finalized block", "number", header.Number, "hash", header.Hash())
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (bc *BlockChain) SetFinalized(header *types.Header) {
bc.currentFinalizedBlock.Store(header)
// Persist to rawdb for recovery on restart
rawdb.WriteFinalizedBlockHash(bc.db, header.Hash())
log.Debug("Set finalized block", "number", header.Number, "hash", header.Hash())
}
func (bc *BlockChain) SetFinalized(header *types.Header) {
if header == nil {
return
}
bc.currentFinalizedBlock.Store(header)
// Persist to rawdb for recovery on restart
rawdb.WriteFinalizedBlockHash(bc.db, header.Hash())
log.Debug("Set finalized block", "number", header.Number, "hash", header.Hash())
}
🤖 Prompt for AI Agents
In `@core/blockchain_l2.go` around lines 263 - 268, SetFinalized may panic or
write an invalid hash when header is nil; add a nil check at the start of
BlockChain.SetFinalized (the method) and return early if header == nil (or log
and return) so you do not call currentFinalizedBlock.Store(nil) nor
rawdb.WriteFinalizedBlockHash with header.Hash(), and avoid accessing
header.Number in log.Debug; only perform Store, rawdb.WriteFinalizedBlockHash
and log.Debug when header is non-nil.

14 changes: 14 additions & 0 deletions core/blockchain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ func (bc *BlockChain) CurrentFastBlock() *types.Block {
return bc.currentFastBlock.Load().(*types.Block)
}

// CurrentSafeBlock retrieves the current safe block of the canonical chain.
// The safe block is derived from L1 batch committed status.
// Returns nil if no safe block has been set yet.
func (bc *BlockChain) CurrentSafeBlock() *types.Header {
return bc.currentSafeBlock.Load()
}

// CurrentFinalizedBlock retrieves the current finalized block of the canonical chain.
// The finalized block is derived from L1 batch finalized status.
// Returns nil if no finalized block has been set yet.
func (bc *BlockChain) CurrentFinalizedBlock() *types.Header {
return bc.currentFinalizedBlock.Load()
}

// 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 {
Expand Down
16 changes: 16 additions & 0 deletions core/rawdb/accessors_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,22 @@ func WriteHeadFastBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
}
}

// ReadFinalizedBlockHash retrieves the hash of the current finalized block.
func ReadFinalizedBlockHash(db ethdb.KeyValueReader) common.Hash {
data, _ := db.Get(headFinalizedBlockKey)
if len(data) == 0 {
return common.Hash{}
}
return common.BytesToHash(data)
}

// WriteFinalizedBlockHash stores the hash of the current finalized block.
func WriteFinalizedBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Put(headFinalizedBlockKey, hash.Bytes()); err != nil {
log.Crit("Failed to store finalized block's hash", "err", err)
}
}

// ReadLastPivotNumber retrieves the number of the last pivot block. If the node
// full synced, the last pivot will always be nil.
func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 {
Expand Down
3 changes: 3 additions & 0 deletions core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ var (
// headFastBlockKey tracks the latest known incomplete block's hash during fast sync.
headFastBlockKey = []byte("LastFast")

// headFinalizedBlockKey tracks the latest known finalized block's hash.
headFinalizedBlockKey = []byte("LastFinalized")

// lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead).
lastPivotKey = []byte("LastPivot")

Expand Down
46 changes: 32 additions & 14 deletions eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,25 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error
_, _, stateDb := api.eth.miner.Pending()
return stateDb.RawDump(opts), nil
}
var block *types.Block
if blockNr == rpc.LatestBlockNumber {
block = api.eth.blockchain.CurrentBlock()
} else {
block = api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
var header *types.Header
switch blockNr {
case rpc.LatestBlockNumber:
header = api.eth.blockchain.CurrentBlock().Header()
case rpc.SafeBlockNumber:
header = api.eth.blockchain.CurrentSafeBlock()
case rpc.FinalizedBlockNumber:
header = api.eth.blockchain.CurrentFinalizedBlock()
default:
block := api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
if block == nil {
return state.Dump{}, fmt.Errorf("block #%d not found", blockNr)
}
header = block.Header()
}
if block == nil {
if header == nil {
return state.Dump{}, fmt.Errorf("block #%d not found", blockNr)
}
stateDb, err := api.eth.BlockChain().StateAt(block.Root())
stateDb, err := api.eth.BlockChain().StateAt(header.Root)
Comment on lines +179 to +197
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Error message shows raw negative block number for safe/finalized.

When CurrentSafeBlock() or CurrentFinalizedBlock() returns nil, the error at Line 195 will display the raw blockNr value (e.g., -4 or -3), which is confusing to users.

Proposed fix with descriptive error messages
 	var header *types.Header
 	switch blockNr {
 	case rpc.LatestBlockNumber:
 		header = api.eth.blockchain.CurrentBlock().Header()
 	case rpc.SafeBlockNumber:
 		header = api.eth.blockchain.CurrentSafeBlock()
+		if header == nil {
+			return state.Dump{}, errors.New("safe block not available")
+		}
 	case rpc.FinalizedBlockNumber:
 		header = api.eth.blockchain.CurrentFinalizedBlock()
+		if header == nil {
+			return state.Dump{}, errors.New("finalized block not available")
+		}
 	default:
 		block := api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
 		if block == nil {
 			return state.Dump{}, fmt.Errorf("block #%d not found", blockNr)
 		}
 		header = block.Header()
 	}
-	if header == nil {
-		return state.Dump{}, fmt.Errorf("block #%d not found", blockNr)
-	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var header *types.Header
switch blockNr {
case rpc.LatestBlockNumber:
header = api.eth.blockchain.CurrentBlock().Header()
case rpc.SafeBlockNumber:
header = api.eth.blockchain.CurrentSafeBlock()
case rpc.FinalizedBlockNumber:
header = api.eth.blockchain.CurrentFinalizedBlock()
default:
block := api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
if block == nil {
return state.Dump{}, fmt.Errorf("block #%d not found", blockNr)
}
header = block.Header()
}
if block == nil {
if header == nil {
return state.Dump{}, fmt.Errorf("block #%d not found", blockNr)
}
stateDb, err := api.eth.BlockChain().StateAt(block.Root())
stateDb, err := api.eth.BlockChain().StateAt(header.Root)
var header *types.Header
switch blockNr {
case rpc.LatestBlockNumber:
header = api.eth.blockchain.CurrentBlock().Header()
case rpc.SafeBlockNumber:
header = api.eth.blockchain.CurrentSafeBlock()
if header == nil {
return state.Dump{}, errors.New("safe block not available")
}
case rpc.FinalizedBlockNumber:
header = api.eth.blockchain.CurrentFinalizedBlock()
if header == nil {
return state.Dump{}, errors.New("finalized block not available")
}
default:
block := api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
if block == nil {
return state.Dump{}, fmt.Errorf("block #%d not found", blockNr)
}
header = block.Header()
}
stateDb, err := api.eth.BlockChain().StateAt(header.Root)
🤖 Prompt for AI Agents
In `@eth/api.go` around lines 179 - 197, When header is nil, the error currently
prints the raw blockNr (which can be negative sentinel values); change the
nil-check/error path around header (after switch that calls
CurrentSafeBlock()/CurrentFinalizedBlock()/CurrentBlock()) to map those sentinel
constants (rpc.LatestBlockNumber, rpc.SafeBlockNumber, rpc.FinalizedBlockNumber)
to human-readable names and return errors like "latest block not found", "safe
block not found", or "finalized block not found" instead of "block `#-4` not
found"; keep the existing behavior for numeric block numbers (the default case
using GetBlockByNumber) and use the blockNr variable only when it represents an
actual numeric block index.

if err != nil {
return state.Dump{}, err
}
Expand Down Expand Up @@ -264,16 +273,25 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta
// the miner and operate on those
_, _, stateDb = api.eth.miner.Pending()
} else {
var block *types.Block
if number == rpc.LatestBlockNumber {
block = api.eth.blockchain.CurrentBlock()
} else {
block = api.eth.blockchain.GetBlockByNumber(uint64(number))
var header *types.Header
switch number {
case rpc.LatestBlockNumber:
header = api.eth.blockchain.CurrentBlock().Header()
case rpc.SafeBlockNumber:
header = api.eth.blockchain.CurrentSafeBlock()
case rpc.FinalizedBlockNumber:
header = api.eth.blockchain.CurrentFinalizedBlock()
default:
block := api.eth.blockchain.GetBlockByNumber(uint64(number))
if block == nil {
return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
}
header = block.Header()
}
if block == nil {
if header == nil {
return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
}
stateDb, err = api.eth.BlockChain().StateAt(block.Root())
stateDb, err = api.eth.BlockChain().StateAt(header.Root)
Comment on lines +276 to +294
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same error message issue in AccountRange.

The error at Line 292 has the same issue as DumpBlock — it will show confusing negative block numbers for safe/finalized requests.

Proposed fix with descriptive error messages
 			var header *types.Header
 			switch number {
 			case rpc.LatestBlockNumber:
 				header = api.eth.blockchain.CurrentBlock().Header()
 			case rpc.SafeBlockNumber:
 				header = api.eth.blockchain.CurrentSafeBlock()
+				if header == nil {
+					return state.IteratorDump{}, errors.New("safe block not available")
+				}
 			case rpc.FinalizedBlockNumber:
 				header = api.eth.blockchain.CurrentFinalizedBlock()
+				if header == nil {
+					return state.IteratorDump{}, errors.New("finalized block not available")
+				}
 			default:
 				block := api.eth.blockchain.GetBlockByNumber(uint64(number))
 				if block == nil {
 					return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
 				}
 				header = block.Header()
 			}
-			if header == nil {
-				return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
-			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var header *types.Header
switch number {
case rpc.LatestBlockNumber:
header = api.eth.blockchain.CurrentBlock().Header()
case rpc.SafeBlockNumber:
header = api.eth.blockchain.CurrentSafeBlock()
case rpc.FinalizedBlockNumber:
header = api.eth.blockchain.CurrentFinalizedBlock()
default:
block := api.eth.blockchain.GetBlockByNumber(uint64(number))
if block == nil {
return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
}
header = block.Header()
}
if block == nil {
if header == nil {
return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
}
stateDb, err = api.eth.BlockChain().StateAt(block.Root())
stateDb, err = api.eth.BlockChain().StateAt(header.Root)
var header *types.Header
switch number {
case rpc.LatestBlockNumber:
header = api.eth.blockchain.CurrentBlock().Header()
case rpc.SafeBlockNumber:
header = api.eth.blockchain.CurrentSafeBlock()
if header == nil {
return state.IteratorDump{}, errors.New("safe block not available")
}
case rpc.FinalizedBlockNumber:
header = api.eth.blockchain.CurrentFinalizedBlock()
if header == nil {
return state.IteratorDump{}, errors.New("finalized block not available")
}
default:
block := api.eth.blockchain.GetBlockByNumber(uint64(number))
if block == nil {
return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
}
header = block.Header()
}
stateDb, err = api.eth.BlockChain().StateAt(header.Root)

if err != nil {
return state.IteratorDump{}, err
}
Expand Down
32 changes: 32 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
if number == rpc.LatestBlockNumber {
return b.eth.blockchain.CurrentBlock().Header(), nil
}
// Safe block
if number == rpc.SafeBlockNumber {
header := b.eth.blockchain.CurrentSafeBlock()
if header == nil {
return nil, errors.New("safe block not available")
}
return header, nil
}
// Finalized block
if number == rpc.FinalizedBlockNumber {
header := b.eth.blockchain.CurrentFinalizedBlock()
if header == nil {
return nil, errors.New("finalized block not available")
}
return header, nil
}
return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
}

Expand Down Expand Up @@ -109,6 +125,22 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
if number == rpc.LatestBlockNumber {
return b.eth.blockchain.CurrentBlock(), nil
}
// Safe block
if number == rpc.SafeBlockNumber {
header := b.eth.blockchain.CurrentSafeBlock()
if header == nil {
return nil, errors.New("safe block not available")
}
return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
}
// Finalized block
if number == rpc.FinalizedBlockNumber {
header := b.eth.blockchain.CurrentFinalizedBlock()
if header == nil {
return nil, errors.New("finalized block not available")
}
return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
}
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
}

Expand Down
29 changes: 29 additions & 0 deletions eth/catalyst/l2_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,32 @@ func (api *l2ConsensusAPI) isVerified(blockHash common.Hash) (executionResult, b
er, found := api.verified[blockHash]
return er, found
}

// SetBlockTags sets the safe and finalized block by hash.
// This is called by the node layer when it determines the safe/finalized
// status based on L1 batch information.
func (api *l2ConsensusAPI) SetBlockTags(safeBlockHash common.Hash, finalizedBlockHash common.Hash) error {
bc := api.eth.BlockChain()

// Set finalized block
if finalizedBlockHash != (common.Hash{}) {
finalizedHeader := bc.GetHeaderByHash(finalizedBlockHash)
if finalizedHeader == nil {
return fmt.Errorf("finalized block %s not found", finalizedBlockHash.Hex())
}
bc.SetFinalized(finalizedHeader)
log.Info("Set finalized block", "number", finalizedHeader.Number, "hash", finalizedBlockHash)
}

// Set safe block
if safeBlockHash != (common.Hash{}) {
safeHeader := bc.GetHeaderByHash(safeBlockHash)
if safeHeader == nil {
return fmt.Errorf("safe block %s not found", safeBlockHash.Hex())
}
bc.SetSafe(safeHeader)
log.Info("Set safe block", "number", safeHeader.Number, "hash", safeBlockHash)
}

return nil
}
55 changes: 44 additions & 11 deletions eth/filters/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,21 +122,55 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
return f.pendingLogs()
}
// Figure out the limits of the filter range
header, _ := f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
if header == nil {
return nil, nil
resolveSpecial := func(number int64) (uint64, error) {
switch number {
case rpc.PendingBlockNumber.Int64(), // inheriting the current logic, pending logs will be disabled in the future
rpc.LatestBlockNumber.Int64():
hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
if hdr == nil {
return 0, errors.New("latest header not found")
}
return hdr.Number.Uint64(), nil
case rpc.SafeBlockNumber.Int64():
hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber)
if hdr == nil {
return 0, errors.New("safe header not found")
}
return hdr.Number.Uint64(), nil
case rpc.FinalizedBlockNumber.Int64():
hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber)
if hdr == nil {
return 0, errors.New("finalized header not found")
}
return hdr.Number.Uint64(), nil
default:
if number < 0 {
return 0, errors.New("negative block number")
}
return uint64(number), nil
}
}

var (
head = header.Number.Uint64()
end = uint64(f.end)
pending = f.end == rpc.PendingBlockNumber.Int64()
begin, end uint64
pending = f.end == rpc.PendingBlockNumber.Int64()
err error
)
if f.begin == rpc.LatestBlockNumber.Int64() {
f.begin = int64(head)

begin, err = resolveSpecial(f.begin)
if err != nil {
return nil, err
}
if f.end == rpc.LatestBlockNumber.Int64() || f.end == rpc.PendingBlockNumber.Int64() {
end = head
end, err = resolveSpecial(f.end)
if err != nil {
return nil, err
}

if begin > end {
return nil, nil
}
f.begin = int64(begin)
f.end = int64(end)

// if maxBlockRange configured then check for
if f.maxBlockRange != -1 && int64(end)-f.begin+1 > f.maxBlockRange {
Expand All @@ -145,7 +179,6 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
// Gather all indexed logs, and finish with non indexed ones
var (
logs []*types.Log
err error
size, sections = f.sys.backend.BloomStatus()
)
if indexed := sections * size; indexed > uint64(f.begin) {
Expand Down
Loading