ethereum/go-ethereum · error
not implemented
Error message
not implemented
What it means
The cleaner type wraps the hash database's node cache so it can be iterated as an ethdb.KeyValueReader-ish interface, but the clean cache is a pure read cache backed by disk; deleting individual keys through it is meaningless (disk is the source of truth). Delete therefore panics to signal an unsupported operation rather than silently doing nothing.
Source
Thrown at triedb/hashdb/database.go:532
c.db.dirties[node.flushPrev].flushNext = node.flushNext
c.db.dirties[node.flushNext].flushPrev = node.flushPrev
}
// Remove the node from the dirty cache
delete(c.db.dirties, hash)
c.db.dirtiesSize -= common.StorageSize(common.HashLength + len(node.node))
if node.external != nil {
c.db.childrenSize -= common.StorageSize(len(node.external) * common.HashLength)
}
// Move the flushed node into the clean cache to prevent insta-reloads
if c.db.cleans != nil {
c.db.cleans.Set(hash[:], rlp)
memcacheCleanWriteMeter.Mark(int64(len(rlp)))
}
return nil
}
func (c *cleaner) Delete(key []byte) error {
panic("not implemented")
}
// Update inserts the dirty nodes in provided nodeset into database and link the
// account trie with multiple storage tries if necessary.
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet) error {
// Ensure the parent state is present and signal a warning if not.
if parent != types.EmptyRootHash {
if blob, _ := db.node(parent); len(blob) == 0 {
log.Error("parent state is not present")
}
}
db.lock.Lock()
defer db.lock.Unlock()
// Insert dirty nodes into the database. In the same tree, it must be
// ensured that children are inserted first, then parent so that children
// can be linked with their parent correctly.
//View on GitHub (pinned to 6bb0588ad8)
Solutions
- Audit the call chain that reaches cleaner.Delete and stop routing write operations to the clean cache
- Perform deletions on the real backing store (freezer/LightPeerDatabase), not the cache wrapper
- If fork semantics genuinely require it, implement Delete as a cache eviction (c.db.cleans.Set(hash, nil) or equivalent) instead of panicking
Example fix
// before
// generic compaction walking all "databases"
for _, db := range handles { db.Delete(key) } // panics on cleaner
// after
for _, db := range handles {
if _, readOnly := db.(*hashdb.cleaner); readOnly { continue }
db.Delete(key)
} Defensive patterns
Strategy: validation
Validate before calling
// Gate cache-backed handles out of write paths
func isReadOnlyCache(db ethdb.Database) bool {
return strings.Contains(fmt.Sprintf("%T", db), "cleaner")
}
if !isReadOnlyCache(target) { target.Delete(key) } Type guard
type deletable interface{ Delete([]byte) error }
func canDelete(db ethdb.Database) bool {
if _, ok := db.(deletable); !ok { return false }
return !isReadOnlyCache(db)
} Prevention
- Pass the underlying LightPeerDatabase/freezer to deletion logic, never cache wrappers
- Keep the cleaner unexported so it cannot leak into consumer signatures
- In fork code, consider not implementing the delete-bearing interface on caches at all
When it happens
Trigger: Calling Delete on the cleaner wrapper exposed by Database (hashdb) — in practice this happens when generic code treats the cache as a general KeyValueStore and invokes delete/compaction paths on it.
Common situations: Fork code that runs compaction (DeleteRange/Delete) against every database handle it can find; accidental reuse of the iterator/cache handle beyond its intended read-only scope; upstream refactors that widened an interface the cleaner implements.
Related errors
- not supported
- duplicated flush operation
- unknown layer type: %T
- nil chainID
- error decoding contract deployer hex %s: %v
AI-assisted analysis of ethereum/go-ethereum@6bb0588ad8 (2026-08-15).
Data as JSON: /api/errors/bf4f09a318099600.
Report an issue: GitHub.