From 92c0695cf39df3e337a6dafac3323b0d064f979c Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 14 Apr 2025 12:52:11 +0200 Subject: [PATCH 1/7] core: back-up to kvdb for a pruned block --- core/rawdb/accessors_chain.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 2f62d86e4b77..9868c47e624a 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -440,6 +440,12 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue // Check if the data is in ancients if isCanon(reader, number, hash) { data, _ = reader.Ancient(ChainFreezerBodiesTable, number) + // The freezer might be pruned. In the particular case of genesis, the block + // will be still available in kvdb. The full genesis block is needed on startup + // sometimes for repair. + if data == nil { + data, _ = db.Get(blockBodyKey(number, hash)) + } return nil } // If not, try reading from leveldb From f62a7e22c98bd64b65ec26c3f2a9ce0e816256d9 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 15 Apr 2025 15:36:43 +0200 Subject: [PATCH 2/7] alternative approach --- core/blockchain.go | 22 ++++++++++++++++++---- core/rawdb/accessors_chain.go | 6 ------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d56996dadbee..f315a4f71b44 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -513,19 +513,33 @@ func (bc *BlockChain) loadLastState() error { log.Warn("Empty database, resetting chain") return bc.Reset() } - // Make sure the entire head block is available - headBlock := bc.GetBlockByHash(head) + headHeader := bc.GetHeaderByHash(head) + if headHeader == nil { + // Corrupt or empty database, init from scratch + log.Warn("Head header missing, resetting chain", "hash", head) + return bc.Reset() + } + + var headBlock *types.Block + if cmp := headHeader.Number.Cmp(new(big.Int)); cmp == 1 { + // Make sure the entire head block is available. + headBlock = bc.GetBlockByHash(head) + } else if cmp == 0 { + // On a pruned node the block body might not be available. But a pruned + // block should never be the head block. The only exception is when, as + // a last resort, chain is reset to genesis. + headBlock = bc.genesisBlock + } if headBlock == nil { // Corrupt or empty database, init from scratch log.Warn("Head block missing, resetting chain", "hash", head) return bc.Reset() } // Everything seems to be fine, set as the head block - bc.currentBlock.Store(headBlock.Header()) + bc.currentBlock.Store(headHeader) headBlockGauge.Update(int64(headBlock.NumberU64())) // Restore the last known head header - headHeader := headBlock.Header() if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) { if header := bc.GetHeaderByHash(head); header != nil { headHeader = header diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 9868c47e624a..2f62d86e4b77 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -440,12 +440,6 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue // Check if the data is in ancients if isCanon(reader, number, hash) { data, _ = reader.Ancient(ChainFreezerBodiesTable, number) - // The freezer might be pruned. In the particular case of genesis, the block - // will be still available in kvdb. The full genesis block is needed on startup - // sometimes for repair. - if data == nil { - data, _ = db.Get(blockBodyKey(number, hash)) - } return nil } // If not, try reading from leveldb From d4b106c9783330936090c673ccd633bf32fdd0dc Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 16 Apr 2025 17:57:08 +0200 Subject: [PATCH 3/7] fix txindexer panic --- core/txindexer.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/txindexer.go b/core/txindexer.go index 31f069995bc3..bffa7a0d3c0e 100644 --- a/core/txindexer.go +++ b/core/txindexer.go @@ -203,9 +203,10 @@ func (indexer *txIndexer) loop(chain *BlockChain) { // Listening to chain events and manipulate the transaction indexes. var ( - stop chan struct{} // Non-nil if background routine is active - done chan struct{} // Non-nil if background routine is active - head = rawdb.ReadHeadBlock(indexer.db).NumberU64() // The latest announced chain head + stop chan struct{} // Non-nil if background routine is active + done chan struct{} // Non-nil if background routine is active + headBlock = rawdb.ReadHeadBlock(indexer.db) // The latest announced chain head + head uint64 headCh = make(chan ChainHeadEvent) sub = chain.SubscribeChainHeadEvent(headCh) @@ -213,13 +214,16 @@ func (indexer *txIndexer) loop(chain *BlockChain) { defer sub.Unsubscribe() // Validate the transaction indexes and repair if necessary - indexer.repair(head) + if headBlock != nil { + indexer.repair(headBlock.NumberU64()) + } // Launch the initial processing if chain is not empty (head != genesis). // This step is useful in these scenarios that chain has no progress. - if head != 0 { + if headBlock != nil && headBlock.NumberU64() != 0 { stop = make(chan struct{}) done = make(chan struct{}) + head = headBlock.Number().Uint64() go indexer.run(head, stop, done) } for { From 60e7867bcd3abbc451abd3ce53f324414a8ac552 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 16 Apr 2025 18:10:53 +0200 Subject: [PATCH 4/7] try fix for setHead --- core/blockchain.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index f315a4f71b44..7c597a6a94bb 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -600,11 +600,15 @@ func (bc *BlockChain) SetHead(head uint64) error { // Send chain head event to update the transaction pool header := bc.CurrentBlock() if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil { - // This should never happen. In practice, previously currentBlock - // contained the entire block whereas now only a "marker", so there - // is an ever so slight chance for a race we should handle. - log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash()) - return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4]) + // In a pruned node the genesis block will not exist in the freezer. + // It should not happen that we set head to any other pruned block. + if header.Number.Uint64() > 0 { + // This should never happen. In practice, previously currentBlock + // contained the entire block whereas now only a "marker", so there + // is an ever so slight chance for a race we should handle. + log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash()) + return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4]) + } } bc.chainHeadFeed.Send(ChainHeadEvent{Header: header}) return nil From aaaea82e6515e313f53891e56101cc73bdef2e94 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 16 Apr 2025 18:11:36 +0200 Subject: [PATCH 5/7] try fix for setHeadWithTimestamp --- core/blockchain.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 7c597a6a94bb..924ade469be9 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -625,11 +625,15 @@ func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error { // Send chain head event to update the transaction pool header := bc.CurrentBlock() if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil { - // This should never happen. In practice, previously currentBlock - // contained the entire block whereas now only a "marker", so there - // is an ever so slight chance for a race we should handle. - log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash()) - return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4]) + // In a pruned node the genesis block will not exist in the freezer. + // It should not happen that we set head to any other pruned block. + if header.Number.Uint64() > 0 { + // This should never happen. In practice, previously currentBlock + // contained the entire block whereas now only a "marker", so there + // is an ever so slight chance for a race we should handle. + log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash()) + return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4]) + } } bc.chainHeadFeed.Send(ChainHeadEvent{Header: header}) return nil From a874ea621f1dfa530412fa92be1bde745c244e15 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Thu, 17 Apr 2025 13:00:00 +0800 Subject: [PATCH 6/7] core: resolve chain head number only --- core/txindexer.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/core/txindexer.go b/core/txindexer.go index bffa7a0d3c0e..64a2e8c49f55 100644 --- a/core/txindexer.go +++ b/core/txindexer.go @@ -196,6 +196,19 @@ func (indexer *txIndexer) repair(head uint64) { } } +// resolveHead resolves the block number of the current chain head. +func (indexer *txIndexer) resolveHead() uint64 { + headBlockHash := rawdb.ReadHeadBlockHash(indexer.db) + if headBlockHash == (common.Hash{}) { + return 0 + } + headBlockNumber := rawdb.ReadHeaderNumber(indexer.db, headBlockHash) + if headBlockNumber == nil { + return 0 + } + return *headBlockNumber +} + // loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending // on the received chain event. func (indexer *txIndexer) loop(chain *BlockChain) { @@ -203,10 +216,9 @@ func (indexer *txIndexer) loop(chain *BlockChain) { // Listening to chain events and manipulate the transaction indexes. var ( - stop chan struct{} // Non-nil if background routine is active - done chan struct{} // Non-nil if background routine is active - headBlock = rawdb.ReadHeadBlock(indexer.db) // The latest announced chain head - head uint64 + stop chan struct{} // Non-nil if background routine is active + done chan struct{} // Non-nil if background routine is active + head = indexer.resolveHead() // The latest announced chain head headCh = make(chan ChainHeadEvent) sub = chain.SubscribeChainHeadEvent(headCh) @@ -214,16 +226,13 @@ func (indexer *txIndexer) loop(chain *BlockChain) { defer sub.Unsubscribe() // Validate the transaction indexes and repair if necessary - if headBlock != nil { - indexer.repair(headBlock.NumberU64()) - } + indexer.repair(head) // Launch the initial processing if chain is not empty (head != genesis). // This step is useful in these scenarios that chain has no progress. - if headBlock != nil && headBlock.NumberU64() != 0 { + if head != 0 { stop = make(chan struct{}) done = make(chan struct{}) - head = headBlock.Number().Uint64() go indexer.run(head, stop, done) } for { From 671ea27fec162fc70d0417c0b311371dd3b2337e Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Thu, 17 Apr 2025 13:07:55 +0800 Subject: [PATCH 7/7] core/txpool: fix panic in txpool reset --- core/txpool/txpool.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index 083aac92c66f..fbfcf5e1492e 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -245,13 +245,15 @@ func (p *TxPool) loop(head *types.Header) { // Try to inject a busy marker and start a reset if successful select { case resetBusy <- struct{}{}: - statedb, err := p.chain.StateAt(newHead.Root) - if err != nil { - log.Crit("Failed to reset txpool state", "err", err) + // Updates the statedb with the new chain head. The head state may be + // unavailable if the initial state sync has not yet completed. + if statedb, err := p.chain.StateAt(newHead.Root); err != nil { + log.Error("Failed to reset txpool state", "err", err) + } else { + p.stateLock.Lock() + p.state = statedb + p.stateLock.Unlock() } - p.stateLock.Lock() - p.state = statedb - p.stateLock.Unlock() // Busy marker injected, start a new subpool reset go func(oldHead, newHead *types.Header) {