diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c02140ff28..bee0f768d1 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -26,6 +26,8 @@ import ( "strings" "unicode" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/external" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -201,6 +203,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { } if ctx.IsSet(utils.OverrideFullImmutabilityThreshold.Name) { params.FullImmutabilityThreshold = ctx.Uint64(utils.OverrideFullImmutabilityThreshold.Name) + downloader.FullMaxForkAncestry = ctx.Uint64(utils.OverrideFullImmutabilityThreshold.Name) } if ctx.IsSet(utils.OverrideMinBlocksForBlobRequests.Name) { params.MinBlocksForBlobRequests = ctx.Uint64(utils.OverrideMinBlocksForBlobRequests.Name) diff --git a/core/block_validator.go b/core/block_validator.go index 2df060644d..8bd5071f12 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -96,7 +96,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { }, func() error { // Withdrawals are present after the Shanghai fork. - if (header.WithdrawalsHash != nil && *header.WithdrawalsHash != common.Hash{}) { + if !header.EmptyWithdrawalsHash() { // Withdrawals list must be present in body after Shanghai. if block.Withdrawals() == nil { return errors.New("missing withdrawals in block body") diff --git a/core/blockchain.go b/core/blockchain.go index 1a9799e1bc..9737613bf0 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1409,6 +1409,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ err error ) if !bc.chainConfig.IsCancun(last.Number(), last.Time()) { + log.Info("WriteAncientBlocks", "startAt", blockChain[0].Number(), "last", last.Number()) writeSize, err = rawdb.WriteAncientBlocks(bc.db, blockChain, receiptChain, td) } else { writeSize, err = rawdb.WriteAncientBlocksAfterCancun(bc.db, bc.chainConfig, blockChain, receiptChain, td) diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index 45d9001802..6f5e18743a 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -280,7 +280,8 @@ func (f *chainFreezer) tryPruneBlobAncient(env *ethdb.FreezerEnv, num uint64) { } start := time.Now() if err = f.ResetTable(ChainFreezerBlobSidecarTable, expectTail, h, false); err != nil { - log.Error("Cannot prune blob ancient", "block", num, "expectTail", expectTail) + log.Error("Cannot prune blob ancient", "block", num, "expectTail", expectTail, "err", err) + return } log.Info("Chain freezer prune useless blobs, now ancient data is", "from", expectTail, "to", num, "cost", common.PrettyDuration(time.Since(start))) } diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index ab2426ca60..e29d8b1e1f 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -342,8 +342,23 @@ func (f *Freezer) TruncateHead(items uint64) (uint64, error) { if oitems <= items { return oitems, nil } - for _, table := range f.tables { - if err := table.truncateHead(items - f.offset); err != nil { + for kind, table := range f.tables { + err := table.truncateHead(items - f.offset) + if err == errTruncationBelowTail { + // This often happens in chain rewinds, but the blob table is special. + // It has the same head, but a different tail from other tables (like bodies, receipts). + // So if the chain is rewound to head below the blob's tail, it needs to reset again. + if kind != ChainFreezerBlobSidecarTable { + return 0, err + } + nt, err := table.resetItems(items-f.offset, items-f.offset) + if err != nil { + return 0, err + } + f.tables[kind] = nt + continue + } + if err != nil { return 0, err } } @@ -469,7 +484,22 @@ func (f *Freezer) repair() error { if slices.Contains(f.additionTableKinds, kind) && EmptyTable(table) { continue } - if err := table.truncateHead(head); err != nil { + err := table.truncateHead(head) + if err == errTruncationBelowTail { + // This often happens in chain rewinds, but the blob table is special. + // It has the same head, but a different tail from other tables (like bodies, receipts). + // So if the chain is rewound to head below the blob's tail, it needs to reset again. + if kind != ChainFreezerBlobSidecarTable { + return err + } + nt, err := table.resetItems(head, head) + if err != nil { + return err + } + f.tables[kind] = nt + continue + } + if err != nil { return err } if err := table.truncateTail(tail); err != nil { @@ -708,7 +738,7 @@ func (f *Freezer) ResetTable(kind string, tail, head uint64, onlyEmpty bool) err f.frozen.Add(f.offset) f.tail.Add(f.offset) f.writeBatch = newFreezerBatch(f) - log.Info("Reset Table", "tail", f.tail.Load(), "frozen", f.frozen.Load()) + log.Info("Reset Table", "kind", kind, "tail", f.tables[kind].itemHidden.Load(), "frozen", f.tables[kind].items.Load()) return nil } diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index 8c58fc1d78..b59cd95606 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -44,6 +44,8 @@ var ( // errNotSupported is returned if the database doesn't support the required operation. errNotSupported = errors.New("this operation is not supported") + + errTruncationBelowTail = errors.New("truncation below tail") ) // indexEntry contains the number/id of the file that the data resides in, as well as the @@ -406,7 +408,7 @@ func (t *freezerTable) truncateHead(items uint64) error { return nil } if items < t.itemHidden.Load() { - return fmt.Errorf("truncation below tail, name: %v, tail: %v, head: %v, truncate to: %v", t.name, t.itemHidden.Load(), t.items.Load(), items) + return errTruncationBelowTail } // We need to truncate, save the old size for metrics tracking oldSize, err := t.sizeNolock() diff --git a/core/types/block.go b/core/types/block.go index bec876d976..9ed0e17e75 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -193,6 +193,12 @@ func (h *Header) EmptyReceipts() bool { return h.ReceiptHash == EmptyReceiptsHash } +// EmptyWithdrawalsHash returns true if there are no WithdrawalsHash for this header/block. +func (h *Header) EmptyWithdrawalsHash() bool { + // TODO(GalaIO): if check EmptyWithdrawalsHash in here? + return h.WithdrawalsHash == nil || *h.WithdrawalsHash == common.Hash{} +} + // Body is a simple (mutable, non-safe) data container for storing and moving // a block's data contents (transactions and uncles) together. type Body struct { diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 1cd8c51b25..480eb03a53 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -874,7 +874,7 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBodyV1 { } // Post-shanghai withdrawals MUST be set to empty slice instead of nil - if withdrawals == nil && block.Header().WithdrawalsHash != nil { + if withdrawals == nil && !block.Header().EmptyWithdrawalsHash() { withdrawals = make([]*types.Withdrawal, 0) } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 1126129522..7e57153c28 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -49,7 +49,7 @@ var ( maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) maxHeadersProcess = 2048 // Number of header download results to import at once into the chain maxResultsProcess = 2048 // Number of content download results to import at once into the chain - fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) + FullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) lightMaxForkAncestry uint64 = params.LightImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) reorgProtThreshold = 48 // Threshold number of recent blocks to disable mini reorg protection @@ -550,8 +550,8 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * // Legacy sync, use the best announcement we have from the remote peer. // TODO(karalabe): Drop this pathway. - if remoteHeight > fullMaxForkAncestry+1 { - d.ancientLimit = remoteHeight - fullMaxForkAncestry - 1 + if remoteHeight > FullMaxForkAncestry+1 { + d.ancientLimit = remoteHeight - FullMaxForkAncestry - 1 } else { d.ancientLimit = 0 } @@ -789,7 +789,7 @@ func (d *Downloader) findAncestor(p *peerConnection, localHeight uint64, remoteH p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight) // Recap floor value for binary search - maxForkAncestry := fullMaxForkAncestry + maxForkAncestry := FullMaxForkAncestry if d.getMode() == LightSync { maxForkAncestry = lightMaxForkAncestry } diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 8214b329ec..84ed6ae50d 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -81,7 +81,7 @@ func newFetchResult(header *types.Header, fastSync bool, pid string) *fetchResul } if !header.EmptyBody() { item.pending.Store(item.pending.Load() | (1 << bodyType)) - } else if header.WithdrawalsHash != nil { + } else if !header.EmptyWithdrawalsHash() { item.Withdrawals = make(types.Withdrawals, 0) } if fastSync && !header.EmptyReceipts() { @@ -788,9 +788,9 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH if uncleListHashes[index] != header.UncleHash { return errInvalidBody } - if header.WithdrawalsHash == nil { + if header.EmptyWithdrawalsHash() { // nil hash means that withdrawals should not be present in body - if withdrawalLists[index] != nil { + if len(withdrawalLists[index]) != 0 { return errInvalidBody } } else { // non-nil hash: body must have withdrawals diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index 46f3febd8b..52a8cedf0a 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -57,7 +57,7 @@ var pregenerated bool func init() { // Reduce some of the parameters to make the tester faster - fullMaxForkAncestry = 10000 + FullMaxForkAncestry = 10000 lightMaxForkAncestry = 10000 blockCacheMaxItems = 1024 fsHeaderSafetyNet = 256 @@ -65,7 +65,7 @@ func init() { testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis) - var forkLen = int(fullMaxForkAncestry + 50) + var forkLen = int(FullMaxForkAncestry + 50) var wg sync.WaitGroup // Generate the test chains to seed the peers with diff --git a/graphql/graphql.go b/graphql/graphql.go index fd782206fb..0fccfa74ff 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -1041,7 +1041,7 @@ func (b *Block) WithdrawalsRoot(ctx context.Context) (*common.Hash, error) { return nil, err } // Pre-shanghai blocks - if header.WithdrawalsHash == nil { + if header.EmptyWithdrawalsHash() { return nil, nil } return header.WithdrawalsHash, nil @@ -1053,7 +1053,7 @@ func (b *Block) Withdrawals(ctx context.Context) (*[]*Withdrawal, error) { return nil, err } // Pre-shanghai blocks - if block.Header().WithdrawalsHash == nil { + if block.Header().EmptyWithdrawalsHash() { return nil, nil } ret := make([]*Withdrawal, 0, len(block.Withdrawals())) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 340768303b..4fbe8bb7f3 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1534,7 +1534,7 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { if head.BaseFee != nil { result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) } - if head.WithdrawalsHash != nil { + if !head.EmptyWithdrawalsHash() { result["withdrawalsRoot"] = head.WithdrawalsHash } if head.BlobGasUsed != nil { @@ -1578,7 +1578,7 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param uncleHashes[i] = uncle.Hash() } fields["uncles"] = uncleHashes - if block.Header().WithdrawalsHash != nil { + if !block.Header().EmptyWithdrawalsHash() { fields["withdrawals"] = block.Withdrawals() } return fields