diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index a540bbc11d..01e42570a9 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -407,6 +407,15 @@ func (bc *BlockChain) stateRecoverable(root common.Hash) bool { return result } +// StateRecoverable checks if the specified state is recoverable by applying +// state histories on top of the persistent state. It's the exported variant +// of stateRecoverable, and shares the same semantics: it's only meaningful for +// the path scheme, and returns false if the state is already available or the +// hash scheme is used. +func (bc *BlockChain) StateRecoverable(root common.Hash) bool { + return bc.stateRecoverable(root) +} + // ContractCodeWithPrefix retrieves a blob of data associated with a contract // hash either from ephemeral in-memory cache, or from persistent storage. func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte { diff --git a/eth/api_backend.go b/eth/api_backend.go index 86dada4108..c4e2508a3d 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -72,6 +72,17 @@ func (b *EthAPIBackend) SetHead(number uint64) error { if pivot := rawdb.ReadLastPivotNumber(b.eth.ChainDb()); pivot != nil && number < *pivot { return fmt.Errorf("rewind target %d is before the snap-sync pivot %d", number, *pivot) } + // In path mode the deepest reachable state is bounded by the amount of state + // histories retained. If the reverse diffs for the target have already been + // pruned, its state is no longer recoverable. + bc := b.eth.blockchain + if bc.TrieDB().Scheme() == rawdb.PathScheme { + if header := bc.GetHeaderByNumber(number); header != nil { + if !bc.HasState(header.Root) && !bc.StateRecoverable(header.Root) { + return errors.New("rewind target is not recoverable") + } + } + } b.eth.handler.downloader.Cancel() return b.eth.blockchain.SetHead(number) }