mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-08 16:01:36 +00:00
Apply review fixes: BAL iterator start (Fix 2), fatal root mismatch when all storage resolved (Fix 3), WriteBlockWithoutState error handling (Fix 4), contract filter construction order (Fix 5), canonical hash backfill (Fix 6), underflow guard in gap processing (Fix 8), O(n²) prepend fix (Fix 9), ReadBALHistory corruption detection (Fix 11), incomplete resolution error (Fix 13), RLP encode panic (Fix 14), gap processing log level (Fix 16), TriggerPartialResync message (Fix 18), and comment accuracy fixes. Remove the stateRoot field and sync.RWMutex from PartialState entirely. Since partial state maintains the full account trie, the computed root always matches the header root (assuming storage root resolution succeeds). ProcessBlockWithBAL now derives parent root from parent.Root() directly, matching how full nodes derive state root from currentBlock headers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
154 lines
5.2 KiB
Go
154 lines
5.2 KiB
Go
// Copyright 2025 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package eth
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
)
|
|
|
|
const (
|
|
// storageRootQueryTimeout is the time to wait for a single snap account query response.
|
|
storageRootQueryTimeout = 5 * time.Second
|
|
|
|
// storageRootMaxRetries is the maximum number of peers to try per unresolved address.
|
|
storageRootMaxRetries = 6
|
|
|
|
// storageRootQueryBytes is the soft response size limit for account range queries.
|
|
// We request a single account, so this is generous.
|
|
storageRootQueryBytes = 4096
|
|
)
|
|
|
|
// ResolveStorageRoots queries snap-capable peers for the storage roots of the
|
|
// given addresses at the specified state root. This is used by partial state
|
|
// nodes to learn the updated storage roots of untracked contracts (whose storage
|
|
// tries are not maintained locally).
|
|
//
|
|
// For each address, the method sends a snap GetAccountRange request scoped to
|
|
// exactly that account's hash. The response contains the full StateAccount
|
|
// including the storage root. If a peer returns the same root as oldRoots[addr],
|
|
// it's considered stale (hasn't processed the block yet) and the next peer is tried.
|
|
func (h *handler) ResolveStorageRoots(
|
|
stateRoot common.Hash,
|
|
addrs []common.Address,
|
|
oldRoots map[common.Address]common.Hash,
|
|
) (map[common.Address]common.Hash, error) {
|
|
if len(addrs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Collect snap-capable peers
|
|
allPeers := h.peers.all()
|
|
var snapPeers []*ethPeer
|
|
for _, p := range allPeers {
|
|
if p.snapExt != nil {
|
|
snapPeers = append(snapPeers, p)
|
|
}
|
|
}
|
|
if len(snapPeers) == 0 {
|
|
return nil, fmt.Errorf("no snap-capable peers available")
|
|
}
|
|
|
|
resolved := make(map[common.Address]common.Hash)
|
|
|
|
for _, addr := range addrs {
|
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
|
|
|
var found bool
|
|
for attempt := 0; attempt < storageRootMaxRetries && attempt < len(snapPeers)*2; attempt++ {
|
|
peer := snapPeers[attempt%len(snapPeers)]
|
|
|
|
root, err := h.queryAccountStorageRoot(peer, stateRoot, addr, addrHash)
|
|
if err != nil {
|
|
log.Trace("Storage root query failed", "addr", addr, "peer", peer.ID(), "err", err)
|
|
continue
|
|
}
|
|
// Check if peer returned a stale root (hasn't processed this block yet)
|
|
if oldRoot, ok := oldRoots[addr]; ok && root == oldRoot {
|
|
log.Trace("Peer returned stale storage root, trying next", "addr", addr, "peer", peer.ID())
|
|
continue
|
|
}
|
|
resolved[addr] = root
|
|
found = true
|
|
log.Debug("Resolved storage root", "addr", addr, "root", root, "peer", peer.ID())
|
|
break
|
|
}
|
|
if !found {
|
|
log.Warn("Failed to resolve storage root", "addr", addr, "attempts", storageRootMaxRetries)
|
|
}
|
|
}
|
|
if len(resolved) < len(addrs) {
|
|
return resolved, fmt.Errorf("resolved %d/%d storage roots", len(resolved), len(addrs))
|
|
}
|
|
return resolved, nil
|
|
}
|
|
|
|
// queryAccountStorageRoot sends a snap GetAccountRange request for a single account
|
|
// and returns its storage root from the response.
|
|
func (h *handler) queryAccountStorageRoot(
|
|
peer *ethPeer,
|
|
stateRoot common.Hash,
|
|
addr common.Address,
|
|
addrHash common.Hash,
|
|
) (common.Hash, error) {
|
|
// Generate unique request ID
|
|
reqID := rand.Uint64()
|
|
|
|
// Create response channel and register it
|
|
respCh := make(chan *snap.AccountRangePacket, 1)
|
|
h.pendingSnapQueries.Store(reqID, respCh)
|
|
|
|
// Clean up on any exit path
|
|
defer h.pendingSnapQueries.Delete(reqID)
|
|
|
|
// Send request: origin = limit = addrHash to request exactly this one account
|
|
if err := peer.snapExt.RequestAccountRange(reqID, stateRoot, addrHash, addrHash, storageRootQueryBytes); err != nil {
|
|
return common.Hash{}, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
// Wait for response with timeout
|
|
select {
|
|
case resp := <-respCh:
|
|
if len(resp.Accounts) == 0 {
|
|
return common.Hash{}, fmt.Errorf("empty response for %s", addr.Hex())
|
|
}
|
|
// Find the account matching our address hash
|
|
for _, acc := range resp.Accounts {
|
|
if acc.Hash == addrHash {
|
|
account, err := types.FullAccount(acc.Body)
|
|
if err != nil {
|
|
return common.Hash{}, fmt.Errorf("failed to decode account: %w", err)
|
|
}
|
|
return account.Root, nil
|
|
}
|
|
}
|
|
return common.Hash{}, fmt.Errorf("account %s not found in response", addr.Hex())
|
|
|
|
case <-time.After(storageRootQueryTimeout):
|
|
return common.Hash{}, fmt.Errorf("timeout waiting for account %s", addr.Hex())
|
|
|
|
case <-h.quitSync:
|
|
return common.Hash{}, fmt.Errorf("handler shutting down")
|
|
}
|
|
}
|