// Copyright 2022 The Erigon Authors
// This file is part of Erigon.
//
// Erigon 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.
//
// Erigon 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 Erigon. If not, see <http://www.gnu.org/licenses/>.

package commitment

import (
	"bytes"
	"cmp"
	"context"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"math/bits"
	"runtime"
	"slices"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	keccak "github.com/erigontech/fastkeccak"

	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/common/dbg"
	"github.com/erigontech/erigon/common/empty"
	"github.com/erigontech/erigon/common/length"
	"github.com/erigontech/erigon/common/log/v3"
	"github.com/erigontech/erigon/db/state/stateifs"
	"github.com/erigontech/erigon/execution/commitment/nibbles"
	"github.com/erigontech/erigon/execution/commitment/trie"
	"github.com/erigontech/erigon/execution/rlp"
)

// DomainPutter is an interface for putting data into domains.
// Used by commitment to write branch data.
type DomainPutter = stateifs.DomainPutter

// CommitmentWrite represents a commitment domain write that needs to be added to changesets.
type CommitmentWrite = stateifs.CommitmentWrite

// CollapseTracer is invoked when a trie node collapses during a commitment update.
// hashedKeyPath is the surviving child's nibble path; branchPrefix is the collapsing branch's.
type CollapseTracer func(hashedKeyPath, branchPrefix []byte)

// witnessTracer receives canonical trie nodes as they are hashed during a
// witness fold pass; nil on the normal commitment path.
type witnessTracer interface {
	onNode(rlp, hash []byte)
}

// witness captures consensus node bytes as they are hashed during a witness
// fold pass. With a nil tracer the trie is on the normal commitment path and
// every method is a no-op. The buffers keep their capacity across pooled reuse,
// so reset() only detaches the tracer.
type witness struct {
	tracer          witnessTracer
	leafBuf         bytes.Buffer
	branchBuf       bytes.Buffer
	leafWriterCache io.Writer // keccak+leafBuf tee, built once per keccak
}

func (w *witness) active() bool { return w.tracer != nil }
func (w *witness) reset()       { w.tracer = nil }

// leafWriter tees the keccak stream into leafBuf so the leaf node bytes can be
// emitted once the hash is read; without a tracer it returns keccak unchanged.
func (w *witness) leafWriter(keccak io.Writer) io.Writer {
	if w.tracer == nil {
		return keccak
	}
	w.leafBuf.Reset()
	if w.leafWriterCache == nil {
		w.leafWriterCache = io.MultiWriter(keccak, &w.leafBuf)
	}
	return w.leafWriterCache
}

func (w *witness) emitLeaf(hash []byte) {
	if w.tracer != nil {
		w.tracer.onNode(w.leafBuf.Bytes(), hash)
	}
}

func (w *witness) beginBranch(prefix []byte) {
	if w.tracer != nil {
		w.branchBuf.Reset()
		w.branchBuf.Write(prefix)
	}
}

// writeBranch appends a hashed slot; callers gate the hot loop on active() so
// this stays a bare Write with no per-slot nil check.
func (w *witness) writeBranch(b []byte) { w.branchBuf.Write(b) }

func (w *witness) emitBranch(hash []byte) {
	if w.tracer != nil {
		w.tracer.onNode(w.branchBuf.Bytes(), hash)
	}
}

// HexPatriciaHashed implements commitment based on patricia merkle tree with radix 16,
// with keys pre-hashed by keccak256
type HexPatriciaHashed struct {
	root cell // Root cell of the tree
	// How many rows (starting from row 0) are currently active and have corresponding selected columns
	// Last active row does not have selected column
	activeRows int
	// Length of the key that reflects current positioning of the grid. It may be larger than number of active rows,
	// if an account leaf cell represents multiple nibbles in the key
	currentKeyLen int16
	accountKeyLen int16
	// Rows of the grid correspond to the level of depth in the patricia tree
	// Columns of the grid correspond to pointers to the nodes further from the root
	grid          [128][16]cell // First 64 rows of this grid are for account trie, and next 64 rows are for storage trie
	currentKey    [128]byte     // For each row indicates which column is currently selected
	depths        [128]int16    // For each row, the depth of cells in that row
	branchBefore  [128]bool     // For each row, whether there was a branch node in the database loaded in unfold
	touchMap      [128]uint16   // For each row, bitmap of cells that were either present before modification, or modified or deleted
	afterMap      [128]uint16   // For each row, bitmap of cells that were present after modification
	keccak        keccak.KeccakState
	keccak2       keccak.KeccakState
	rootChecked   bool // Set to false if it is not known whether the root is empty, set to true if it is checked
	rootTouched   bool
	rootPresent   bool
	traceW        io.Writer // nil = disabled, non-nil = write trace output
	ctx           PatriciaContext
	hashAuxBuffer [128]byte           // buffer to compute cell hash or write hash-related things
	cellHashBuf   common.Hash         // shared scratch buffer for hashKey calls (avoids per-cell allocation)
	leafHashBuf   [33]byte            // shared scratch for leaf hash prefixing (avoids per-leaf escape)
	leafRlpBuf    [maxLeafRlpLen]byte // shared scratch for a leaf's RLP list prefix + compact key
	rlpPrefixBuf  [8]byte             // shared scratch for RlpSerializable length prefixes
	auxBuffer     *bytes.Buffer       // auxiliary buffer used during branch updates encoding
	branchEncoder *BranchEncoder

	mounted    bool  // true if this trie is mounted to some root trie
	mountedNib int   // if 0 <= nib <= 15 means mounted to some root. If -1, means it's a storage subtrie so must not be folded above depth 63
	mountWall  int16 // depth the mounted subtree folds down to (split depth + 1); foldMounted stops here

	memoizationOff bool // if true, do not rely on memoized hashes
	//temp buffers
	accValBuf rlp.RlpEncodedBytes

	// leaveDeferredForCaller when true, Process() leaves deferred updates on the branchEncoder
	// for the caller to handle via TakeDeferredUpdates(). When false (default), Process()
	// applies deferred updates inline.
	leaveDeferredForCaller bool

	// collapseTracer is called when a node collapse occurs (FullNode reduced to single child).
	// Used by witness generation to capture paths that need resolution.
	collapseTracer CollapseTracer

	witness witness

	cfg TrieConfig // static config, set at construction

	//processing metrics
	metrics       *Metrics
	depthsToTxNum [129]uint64 // endTxNum of file with branch data for that depth

	// lastUpdateCellWasEmpty reports whether the most recent updateCell stamped a key
	// into an empty cell — i.e. the key is absent from the pre-state trie, so nothing
	// can exist on disk beneath it.
	lastUpdateCellWasEmpty bool
	hadToLoadL             map[uint64]skipStat
}

// Clones current trie state to allow concurrent processing.
func (hph *HexPatriciaHashed) SpawnSubTrie(ctx PatriciaContext, forNibble int) *HexPatriciaHashed {
	subCfg := hph.cfg.Subtrie()
	subTrie := NewHexPatriciaHashed(hph.accountKeyLen, ctx, subCfg)

	subTrie.mountTo(hph, forNibble)
	return subTrie
}

var hphPool sync.Pool

func NewHexPatriciaHashed(accountKeyLen int16, ctx PatriciaContext, cfg TrieConfig) *HexPatriciaHashed {
	hph, ok := hphPool.Get().(*HexPatriciaHashed)
	if !ok {
		hph = newHexPatriciaHashed()
	}
	// No resetForReuse() needed — Release() already cleaned the object,
	// and newHexPatriciaHashed() produces a zero-state struct.
	hph.accountKeyLen = accountKeyLen
	hph.ctx = ctx
	hph.applyConfig(cfg)
	return hph
}

// applyConfig stores the config and applies its fields to the trie.
func (hph *HexPatriciaHashed) applyConfig(cfg TrieConfig) {
	hph.cfg = cfg
	hph.branchEncoder.setDeferUpdates(cfg.DeferBranchUpdates)
	hph.branchEncoder.maxDeferredUpdates = DefaultMaxDeferredUpdates
	hph.leaveDeferredForCaller = cfg.LeaveDeferredForCaller
	hph.memoizationOff = cfg.MemoizationOff
	hph.metrics.SetCsvMetrics(cfg.CsvMetricsFilePrefix)
}

func newHexPatriciaHashed() *HexPatriciaHashed {
	hph := &HexPatriciaHashed{
		keccak:        keccak.NewFastKeccak(),
		keccak2:       keccak.NewFastKeccak(),
		auxBuffer:     bytes.NewBuffer(make([]byte, 8192)),
		hadToLoadL:    make(map[uint64]skipStat),
		accValBuf:     make(rlp.RlpEncodedBytes, 128),
		metrics:       NewMetrics(""),
		branchEncoder: NewBranchEncoder(1024),
	}

	hph.branchEncoder.setMetrics(hph.metrics)
	return hph
}

// SetCollapseTracer sets a callback that will be invoked when a node collapse occurs
// during commitment calculation. This is used by witness generation to capture paths
// to HashNodes that need resolution when a FullNode is reduced to a single child.
func (hph *HexPatriciaHashed) SetCollapseTracer(tracer CollapseTracer) {
	hph.collapseTracer = tracer
}

// resetForReuse resets all mutable state so a pooled HexPatriciaHashed is safe to reuse.
// The large grid array is NOT zeroed — activeRows=0 means no cells are live,
// and cells are properly initialized via cell.reset() during unfold/fold.
func (hph *HexPatriciaHashed) resetForReuse() {
	// SetState(nil) resets: root, rootTouched, rootChecked, rootPresent,
	// currentKeyLen, activeRows, depths, branchBefore, touchMap, afterMap.
	hph.root.reset()
	hph.rootTouched = false
	hph.rootChecked = false
	hph.rootPresent = false
	hph.currentKeyLen = 0
	hph.activeRows = 0
	for i := range hph.depths {
		hph.depths[i] = 0
		hph.branchBefore[i] = false
		hph.touchMap[i] = 0
		hph.afterMap[i] = 0
	}

	// ctx — set by caller after pool get
	hph.ctx = nil

	// reuse map, don't reallocate
	clear(hph.hadToLoadL)

	hph.mounted = false
	hph.mountedNib = 0
	hph.mountWall = 0

	// tracing
	hph.traceW = nil
	hph.collapseTracer = nil
	hph.witness.reset()

	// flags — reset to zero values; applyConfig will restore from stored cfg
	hph.memoizationOff = false
	hph.leaveDeferredForCaller = false

	// auxiliary buffer
	hph.auxBuffer.Reset()

	// branch encoder: clear deferred updates, reset buffer
	hph.branchEncoder.ClearDeferred()
	hph.branchEncoder.buf.Reset()
	hph.branchEncoder.setDeferUpdates(false) // will be re-set by applyConfig

	// reset config to zero — caller sets via applyConfig after pool get
	hph.cfg = TrieConfig{}

	// depth-to-txnum mapping
	clear(hph.depthsToTxNum[:])
}

// Release clears all mutable state and returns this HexPatriciaHashed to the pool for reuse.
// This ensures no stale database cursor references survive in the pool and releases large
// transient data (maps, buffers) for GC promptly. After calling Release, the caller must
// not use the struct.
func (hph *HexPatriciaHashed) Release() {
	hph.resetForReuse()
	hphPool.Put(hph)
}

type cell struct {
	hashedExtension [128]byte
	extension       [64]byte
	accountAddr     common.Address                  // account plain key
	storageAddr     [length.Addr + length.Hash]byte // storage plain key
	hash            common.Hash                     // cell hash
	stateHash       common.Hash
	hashedExtLen    int16     // length of the hashed extension, if any
	extLen          int16     // length of the extension, if any
	accountAddrLen  int16     // length of account plain key
	storageAddrLen  int16     // length of the storage plain key
	hashLen         int16     // Length of the hash (or embedded)
	stateHashLen    int16     // stateHash length, if > 0 can reuse
	loaded          loadFlags // folded Cell have only hash, unfolded have all fields
	Update                    // state update
}

type loadFlags uint8

const (
	cellLoadNone    = loadFlags(0)
	cellLoadAccount = loadFlags(1)
	cellLoadStorage = loadFlags(2)
)

// maxLeafRlpLen bounds a leaf's assembled RLP header: list prefix (tag plus up to
// 8 length bytes), key prefix byte, then the compact key. Derived from the nibble
// array rather than from the shorter keys today's depth arithmetic yields, so a
// caller slicing deeper cannot overflow the buffer.
const maxLeafRlpLen = 9 + 1 + (len(cell{}.hashedExtension)/2 + 1)

func (f loadFlags) String() string {
	var b strings.Builder
	if f == cellLoadNone {
		b.WriteString("false")
	} else {
		if f.account() {
			b.WriteString("Account ")
		}
		if f.storage() {
			b.WriteString("Storage ")
		}
	}
	return b.String()
}

func (f loadFlags) account() bool {
	return f&cellLoadAccount != 0
}

func (f loadFlags) storage() bool {
	return f&cellLoadStorage != 0
}

func (f loadFlags) addFlag(loadFlags loadFlags) loadFlags {
	if loadFlags == cellLoadNone {
		return f
	}
	return f | loadFlags
}

var (
	emptyRootHashBytes = empty.RootHash[:]
)

func (cell *cell) hashAccKey(keccak keccak.KeccakState, depth int16, hashBuf []byte) error {
	return hashKey(keccak, cell.accountAddr[:cell.accountAddrLen], cell.hashedExtension[:], depth, hashBuf)
}

func (cell *cell) hashStorageKey(keccak keccak.KeccakState, accountKeyLen, downOffset int16, hashedKeyOffset int16, hashBuf []byte) error {
	return hashKey(keccak, cell.storageAddr[accountKeyLen:cell.storageAddrLen], cell.hashedExtension[downOffset:], hashedKeyOffset, hashBuf)
}

func (cell *cell) reset() {
	cell.accountAddrLen = 0
	cell.storageAddrLen = 0
	cell.hashedExtLen = 0
	cell.extLen = 0
	cell.hashLen = 0
	cell.stateHashLen = 0
	cell.loaded = cellLoadNone
	clear(cell.hashedExtension[:])
	clear(cell.extension[:])
	clear(cell.accountAddr[:])
	clear(cell.storageAddr[:])
	clear(cell.hash[:])
	cell.Update.Reset()
}

func (cell *cell) FullString() string {
	b := new(strings.Builder)
	b.WriteString("{")
	b.WriteString(fmt.Sprintf("loaded=%v", cell.loaded))
	if cell.Deleted() {
		b.WriteString(" DELETED ")
	}

	if cell.accountAddrLen > 0 {
		b.WriteString(fmt.Sprintf(" addr=%x", cell.accountAddr[:cell.accountAddrLen]))
		b.WriteString(fmt.Sprintf(" balance=%s", cell.Balance.String()))
		b.WriteString(fmt.Sprintf(" nonce=%d", cell.Nonce))
		if cell.CodeHash != empty.CodeHash {
			b.WriteString(fmt.Sprintf(" codeHash=%x", cell.CodeHash[:]))
		} else {
			b.WriteString(" codeHash=EMPTY")
		}
	}
	if cell.storageAddrLen > 0 {
		b.WriteString(fmt.Sprintf(" addr[s]=%x", cell.storageAddr[:cell.storageAddrLen]))
		b.WriteString(fmt.Sprintf(" storage=%x", cell.Storage[:cell.StorageLen]))
	}
	if cell.hashLen > 0 {
		b.WriteString(fmt.Sprintf(" h=%x", cell.hash[:cell.hashLen]))
	}
	if cell.stateHashLen > 0 {
		b.WriteString(fmt.Sprintf(" memHash=%x", cell.stateHash[:cell.stateHashLen]))
	}
	if cell.extLen > 0 {
		b.WriteString(fmt.Sprintf(" extension=%x", cell.extension[:cell.extLen]))
	}
	if cell.hashedExtLen > 0 {
		b.WriteString(fmt.Sprintf(" hashedExtension=%x", cell.hashedExtension[:cell.hashedExtLen]))
	}

	b.WriteString("}")
	return b.String()
}

func (cell *cell) setFromUpdate(update *Update) {
	cell.Update.Merge(update)
	if update.Flags&StorageUpdate != 0 {
		cell.loaded = cell.loaded.addFlag(cellLoadStorage)
		hadToLoad.Add(1)
	}
	if update.Flags&BalanceUpdate != 0 || update.Flags&NonceUpdate != 0 || update.Flags&CodeUpdate != 0 {
		cell.loaded = cell.loaded.addFlag(cellLoadAccount)
		hadToLoad.Add(1)
	}
}

func (cell *cell) fillFromUpperCell(upCell *cell, depth, depthIncrement int16) {
	if upCell.hashedExtLen >= depthIncrement {
		cell.hashedExtLen = upCell.hashedExtLen - depthIncrement
	} else {
		cell.hashedExtLen = 0
	}
	if upCell.hashedExtLen > depthIncrement {
		copy(cell.hashedExtension[:], upCell.hashedExtension[depthIncrement:upCell.hashedExtLen])
	}
	if upCell.extLen >= depthIncrement {
		cell.extLen = upCell.extLen - depthIncrement
	} else {
		cell.extLen = 0
	}
	if upCell.extLen > depthIncrement {
		copy(cell.extension[:], upCell.extension[depthIncrement:upCell.extLen])
	}
	if depth <= 64 {
		cell.accountAddrLen = upCell.accountAddrLen
		if upCell.accountAddrLen > 0 {
			copy(cell.accountAddr[:], upCell.accountAddr[:cell.accountAddrLen])
			cell.Balance.Set(&upCell.Balance)
			cell.Nonce = upCell.Nonce
			cell.CodeHash = upCell.CodeHash
			cell.extLen = upCell.extLen
			if upCell.extLen > 0 {
				copy(cell.extension[:], upCell.extension[:upCell.extLen])
			}
		}
	} else {
		cell.accountAddrLen = 0
	}
	cell.storageAddrLen = upCell.storageAddrLen
	if upCell.storageAddrLen > 0 {
		copy(cell.storageAddr[:], upCell.storageAddr[:upCell.storageAddrLen])
		cell.StorageLen = upCell.StorageLen
		if upCell.StorageLen > 0 {
			copy(cell.Storage[:], upCell.Storage[:upCell.StorageLen])
		}
	}
	cell.hashLen = upCell.hashLen
	if upCell.hashLen > 0 {
		copy(cell.hash[:], upCell.hash[:upCell.hashLen])
	}
	cell.loaded = upCell.loaded
}

// fillFromLowerCell fills the cell with the data from the cell of the lower row during fold
func (cell *cell) fillFromLowerCell(lowCell *cell, lowDepth int16, preExtension []byte, nibble int) {
	if lowCell.accountAddrLen > 0 || lowDepth < 64 {
		cell.accountAddrLen = lowCell.accountAddrLen
	}
	if lowCell.accountAddrLen > 0 {
		copy(cell.accountAddr[:], lowCell.accountAddr[:cell.accountAddrLen])
		cell.Balance.Set(&lowCell.Balance)
		cell.Nonce = lowCell.Nonce
		cell.CodeHash = lowCell.CodeHash
	}
	cell.storageAddrLen = lowCell.storageAddrLen
	if lowCell.storageAddrLen > 0 {
		copy(cell.storageAddr[:], lowCell.storageAddr[:cell.storageAddrLen])
		cell.StorageLen = lowCell.StorageLen
		if lowCell.StorageLen > 0 {
			copy(cell.Storage[:], lowCell.Storage[:lowCell.StorageLen])
		}
	}
	if lowCell.hashLen > 0 {
		if (lowCell.accountAddrLen == 0 && lowDepth < 64) || (lowCell.storageAddrLen == 0 && lowDepth > 64) {
			// Extension is related to either accounts branch node, or storage branch node, we prepend it by preExtension | nibble
			if len(preExtension) > 0 {
				copy(cell.extension[:], preExtension)
			}
			cell.extension[len(preExtension)] = byte(nibble)
			if lowCell.extLen > 0 {
				copy(cell.extension[1+len(preExtension):], lowCell.extension[:lowCell.extLen])
			}
			cell.extLen = lowCell.extLen + 1 + int16(len(preExtension))
			if cell.accountAddrLen == 0 && cell.storageAddrLen == 0 {
				copy(cell.hashedExtension[:], cell.extension[:cell.extLen])
				cell.hashedExtLen = cell.extLen
			}
		} else {
			// Extension is related to a storage branch node, so we copy it upwards as is
			cell.extLen = lowCell.extLen
			if lowCell.extLen > 0 {
				copy(cell.extension[:], lowCell.extension[:lowCell.extLen])
			}
		}
	}
	cell.hashLen = lowCell.hashLen
	if lowCell.hashLen > 0 {
		copy(cell.hash[:], lowCell.hash[:lowCell.hashLen])
	}
	if lowDepth > 64 {
		cell.loaded = cell.loaded.addFlag(lowCell.loaded)
	} else {
		cell.loaded = lowCell.loaded
	}
}

func (cell *cell) deriveHashedKeys(depth int16, keccak keccak.KeccakState, accountKeyLen int16, hashBuf []byte) error {
	extraLen := int16(0)
	if cell.accountAddrLen > 0 {
		if depth > 64 {
			return errors.New("deriveHashedKeys accountAddr present at depth > 64")
		}
		extraLen = 64 - depth
	}
	if cell.storageAddrLen > 0 {
		if depth >= 64 {
			extraLen = 128 - depth
		} else {
			extraLen += 64
		}
	}
	if extraLen > 0 {
		if cell.hashedExtLen > 0 {
			copy(cell.hashedExtension[extraLen:], cell.hashedExtension[:cell.hashedExtLen])
		}
		cell.hashedExtLen = min(extraLen+cell.hashedExtLen, int16(len(cell.hashedExtension)))
		var hashedKeyOffset, downOffset int16
		if cell.accountAddrLen > 0 {
			if err := cell.hashAccKey(keccak, depth, hashBuf); err != nil {
				return err
			}
			downOffset = 64 - depth
		}
		if cell.storageAddrLen > 0 {
			if depth >= 64 {
				hashedKeyOffset = depth - 64
			}
			if depth == 0 {
				accountKeyLen = 0
			}
			if err := cell.hashStorageKey(keccak, accountKeyLen, downOffset, hashedKeyOffset, hashBuf); err != nil {
				return err
			}
		}
	}
	return nil
}

func (cell *cell) fillFromFields(data []byte, pos int, fieldBits cellFields) (int, error) {
	fields := []struct {
		flag      cellFields
		lenField  *int16
		dataField []byte
		extraFunc func(int16)
	}{
		{fieldExtension, &cell.hashedExtLen, cell.hashedExtension[:], func(l int16) {
			cell.extLen = l
			if l > 0 {
				copy(cell.extension[:], cell.hashedExtension[:l])
			}
		}},
		{fieldAccountAddr, &cell.accountAddrLen, cell.accountAddr[:], nil},
		{fieldStorageAddr, &cell.storageAddrLen, cell.storageAddr[:], nil},
		{fieldHash, &cell.hashLen, cell.hash[:], nil},
		{fieldStateHash, &cell.stateHashLen, cell.stateHash[:], nil},
	}

	for _, f := range fields {
		if fieldBits&f.flag != 0 {
			l, n, err := readUvarint(data[pos:])
			if err != nil {
				return 0, err
			}
			pos += n

			if len(data) < pos+int(l) {
				return 0, fmt.Errorf("buffer too small for %v", f.flag)
			}

			*f.lenField = int16(l)
			if l > 0 {
				copy(f.dataField, data[pos:pos+int(l)])
				pos += int(l)
			}
			if f.extraFunc != nil {
				f.extraFunc(int16(l))
			}
		} else {
			*f.lenField = 0
			if f.flag == fieldExtension {
				cell.extLen = 0
			}
		}
	}

	if fieldBits&fieldAccountAddr != 0 {
		cell.CodeHash = empty.CodeHash
	}
	return pos, nil
}

func readUvarint(data []byte) (uint64, int, error) {
	l, n := binary.Uvarint(data)
	if n == 0 {
		return 0, 0, errors.New("buffer too small for length")
	} else if n < 0 {
		return 0, 0, errors.New("value overflow for length")
	}
	return l, n, nil
}

// skipCellFields skips over the fields in branch data based on fieldBits.
// Used during warmup traversal to find the right child's data.
func skipCellFields(data []byte, pos int, fieldBits byte) int {
	// Field flags: extension=1, accountAddr=2, storageAddr=4, hash=8, stateHash=16
	for bit := byte(1); bit <= 16; bit <<= 1 {
		if fieldBits&bit != 0 {
			if pos >= len(data) {
				return pos
			}
			// Read length varint
			l, n := binary.Uvarint(data[pos:])
			if n <= 0 {
				return pos
			}
			pos += n + int(l) // Skip length + data
		}
	}
	return pos
}

func (cell *cell) accountForHashing(buffer []byte, storageRootHash common.Hash) int {
	balanceBytes := 0
	if !cell.Balance.LtUint64(128) {
		balanceBytes = cell.Balance.ByteLen()
	}

	var nonceBytes int
	if cell.Nonce < 128 && cell.Nonce != 0 {
		nonceBytes = 0
	} else {
		nonceBytes = common.BitLenToByteLen(bits.Len64(cell.Nonce))
	}

	var structLength = uint(balanceBytes + nonceBytes + 2)
	structLength += 66 // Two 32-byte arrays + 2 prefixes

	var pos int
	if structLength < 56 {
		buffer[0] = byte(192 + structLength)
		pos = 1
	} else {
		lengthBytes := common.BitLenToByteLen(bits.Len(structLength))
		buffer[0] = byte(247 + lengthBytes)

		for i := lengthBytes; i > 0; i-- {
			buffer[i] = byte(structLength)
			structLength >>= 8
		}

		pos = lengthBytes + 1
	}

	// Encoding nonce
	if cell.Nonce < 128 && cell.Nonce != 0 {
		buffer[pos] = byte(cell.Nonce)
	} else {
		buffer[pos] = byte(128 + nonceBytes)
		var nonce = cell.Nonce
		for i := nonceBytes; i > 0; i-- {
			buffer[pos+i] = byte(nonce)
			nonce >>= 8
		}
	}
	pos += 1 + nonceBytes

	// Encoding balance
	if cell.Balance.LtUint64(128) && !cell.Balance.IsZero() {
		buffer[pos] = byte(cell.Balance.Uint64())
		pos++
	} else {
		buffer[pos] = byte(128 + balanceBytes)
		pos++
		cell.Balance.WriteToSlice(buffer[pos : pos+balanceBytes])
		pos += balanceBytes
	}

	// Encoding Root and CodeHash
	buffer[pos] = 128 + 32
	pos++
	copy(buffer[pos:], storageRootHash[:])
	pos += 32
	buffer[pos] = 128 + 32
	pos++
	copy(buffer[pos:], cell.CodeHash[:])
	pos += 32
	return pos
}

func (hph *HexPatriciaHashed) completeLeafHash(buf []byte, compactLen int, key []byte, compact0 byte, ni int, val rlp.RlpSerializable, singleton bool) ([]byte, error) {
	// Compute the total length of binary representation
	var kp, kl int
	if compactLen > 1 {
		kp = 1
		kl = compactLen
	} else {
		kl = 1
	}

	totalLen := kp + kl + val.DoubleRLPLen()
	// The header (list prefix, key prefix, compact key) is assembled into one scratch
	// buffer: passing per-write stack arrays to the io.Writer interface heap-allocates them.
	header := hph.leafRlpBuf[:]
	pl := rlp.EncodeListPrefixToBuf(totalLen, header)
	n := pl
	if kp > 0 {
		header[n] = 0x80 + byte(compactLen)
		n++
	}
	header[n] = compact0
	n++
	for i := 1; i < compactLen; i++ {
		header[n] = key[ni]*16 + key[ni+1]
		n++
		ni += 2
	}

	canEmbed := !singleton && totalLen+pl < length.Hash
	var writer io.Writer
	if canEmbed {
		//hph.byteArrayWriter.Setup(buf)
		hph.auxBuffer.Reset()
		writer = hph.auxBuffer
	} else {
		hph.keccak.Reset()
		writer = hph.witness.leafWriter(hph.keccak)
	}
	if _, err := writer.Write(header[:n]); err != nil {
		return nil, err
	}
	if err := val.ToDoubleRLP(writer, hph.rlpPrefixBuf[:]); err != nil {
		return nil, err
	}
	if canEmbed {
		buf = hph.auxBuffer.Bytes()
	} else {
		hph.leafHashBuf[0] = 0x80 + length.Hash
		if _, err := hph.keccak.Read(hph.leafHashBuf[1:]); err != nil {
			return nil, err
		}
		buf = append(buf, hph.leafHashBuf[:]...)
		hph.witness.emitLeaf(hph.leafHashBuf[1:])
	}
	return buf, nil
}

func (hph *HexPatriciaHashed) leafHashWithKeyVal(buf, key []byte, val rlp.RlpSerializableBytes, singleton bool) ([]byte, error) {
	// Write key
	var compactLen int
	var ni int
	var compact0 byte
	compactLen = (len(key)-1)/2 + 1
	if len(key)&1 == 0 {
		compact0 = 0x30 + key[0] // Odd: (3<<4) + first nibble
		ni = 1
	} else {
		compact0 = 0x20
	}
	return hph.completeLeafHash(buf, compactLen, key, compact0, ni, val, singleton)
}

func (hph *HexPatriciaHashed) accountLeafHashWithKey(buf, key []byte, val rlp.RlpSerializable) ([]byte, error) {
	// Write key
	var compactLen int
	var ni int
	var compact0 byte
	if nibbles.HasTerm(key) {
		compactLen = (len(key)-1)/2 + 1
		if len(key)&1 == 0 {
			compact0 = 48 + key[0] // Odd (1<<4) + first nibble
			ni = 1
		} else {
			compact0 = 32
		}
	} else {
		compactLen = len(key)/2 + 1
		if len(key)&1 == 1 {
			compact0 = terminatorHexByte + key[0] // Odd (1<<4) + first nibble
			ni = 1
		}
	}
	return hph.completeLeafHash(buf, compactLen, key, compact0, ni, val, true)
}

func (hph *HexPatriciaHashed) extensionHash(key []byte, hash []byte) (common.Hash, error) {
	var hashBuf common.Hash

	// Compute the total length of binary representation
	var kp, kl int
	// Write key
	var compactLen int
	var ni int
	var compact0 byte
	if nibbles.HasTerm(key) {
		compactLen = (len(key)-1)/2 + 1
		if len(key)&1 == 0 {
			compact0 = 0x30 + key[0] // Odd: (3<<4) + first nibble
			ni = 1
		} else {
			compact0 = 0x20
		}
	} else {
		compactLen = len(key)/2 + 1
		if len(key)&1 == 1 {
			compact0 = 0x10 + key[0] // Odd: (1<<4) + first nibble
			ni = 1
		}
	}
	var keyPrefix [1]byte
	if compactLen > 1 {
		keyPrefix[0] = 0x80 + byte(compactLen)
		kp = 1
		kl = compactLen
	} else {
		kl = 1
	}
	totalLen := kp + kl + 33
	var lenPrefix [4]byte
	pt := rlp.EncodeListPrefixToBuf(totalLen, lenPrefix[:])

	hph.keccak.Reset()
	w := hph.witness.leafWriter(hph.keccak)
	if _, err := w.Write(lenPrefix[:pt]); err != nil {
		return hashBuf, err
	}
	if _, err := w.Write(keyPrefix[:kp]); err != nil {
		return hashBuf, err
	}
	var b [1]byte
	b[0] = compact0
	if _, err := w.Write(b[:]); err != nil {
		return hashBuf, err
	}
	for i := 1; i < compactLen; i++ {
		b[0] = key[ni]*16 + key[ni+1]
		if _, err := w.Write(b[:]); err != nil {
			return hashBuf, err
		}
		ni += 2
	}
	b[0] = 0x80 + length.Hash
	if _, err := w.Write(b[:]); err != nil {
		return hashBuf, err
	}
	if _, err := w.Write(hash); err != nil {
		return hashBuf, err
	}
	// Replace previous hash with the new one
	if _, err := hph.keccak.Read(hashBuf[:]); err != nil {
		return hashBuf, err
	}
	hph.witness.emitLeaf(hashBuf[:])
	return hashBuf, nil
}

func (hph *HexPatriciaHashed) computeCellHashLen(cell *cell, depth int16) int16 {
	if cell.storageAddrLen > 0 && depth >= 64 {
		if cell.stateHashLen > 0 {
			return cell.stateHashLen + 1
		}

		keyLen := 128 - depth + 1 // Length of hex key with terminator character
		var kp, kl int
		compactLen := (keyLen-1)/2 + 1
		if compactLen > 1 {
			kp = 1
			kl = int(compactLen)
		} else {
			kl = 1
		}
		val := rlp.RlpSerializableBytes(cell.Storage[:cell.StorageLen])
		totalLen := kp + kl + val.DoubleRLPLen()
		var lenPrefix [4]byte
		pt := rlp.EncodeListPrefixToBuf(totalLen, lenPrefix[:])
		if totalLen+pt < length.Hash {
			return int16(totalLen + pt)
		}
	}
	return length.Hash + 1
}

func (hph *HexPatriciaHashed) witnessComputeCellHashWithStorage(cell *cell, depth int16, buf []byte) ([]byte, bool, []byte, error) {
	var err error
	var storageRootHash common.Hash
	var storageRootHashIsSet bool
	if hph.memoizationOff {
		cell.stateHashLen = 0 // Reset stateHashLen to force recompute
	}

	// Use a temporary buffer for hashed key computation to avoid corrupting cell.hashedExtension
	// which may be needed for subsequent witness operations on other keys
	// note that the cell.hashedExtension overwrite is still present in the `computeCellHash()`
	var hashedKeyBuf [128]byte

	if cell.storageAddrLen > 0 {
		var hashedKeyOffset int16
		if depth >= 64 {
			hashedKeyOffset = depth - 64
		}
		singleton := depth <= 64
		koffset := hph.accountKeyLen
		if depth == 0 && cell.accountAddrLen == 0 {
			// if account key is empty, then we need to hash storage key from the key beginning
			koffset = 0
		}
		if err = hashKey(hph.keccak, cell.storageAddr[koffset:cell.storageAddrLen], hashedKeyBuf[:], hashedKeyOffset, hph.cellHashBuf[:]); err != nil {
			return nil, storageRootHashIsSet, nil, err
		}
		hashedKeyBuf[64-hashedKeyOffset] = terminatorHexByte // Add terminator

		if cell.stateHashLen > 0 {
			res := append([]byte{160}, cell.stateHash[:cell.stateHashLen]...)
			hph.keccak.Reset()
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "REUSED stateHash %x spk %x\n", res, cell.storageAddr[:cell.storageAddrLen])
			}
			skippedLoad.Add(1)
			if !singleton {
				return res, storageRootHashIsSet, nil, err
			} else {
				storageRootHashIsSet = true
				storageRootHash = *(*common.Hash)(res[1:])
				//copy(storageRootHash[:], res[1:])
				//cell.stateHashLen = 0
			}
		} else {
			if !cell.loaded.storage() {
				hph.metrics.StorageLoad(cell.storageAddr[:cell.storageAddrLen])
				update, err := hph.storageFromCacheOrDB(cell.storageAddr[:cell.storageAddrLen])
				if err != nil {
					return nil, storageRootHashIsSet, nil, err
				}
				cell.setFromUpdate(update)
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "Storage %x was not loaded\n", cell.storageAddr[:cell.storageAddrLen])
				}
			}
			if singleton {
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "leafHashWithKeyVal(singleton) for [%x]=>[%x]\n", hashedKeyBuf[:64-hashedKeyOffset+1], cell.Storage[:cell.StorageLen])
				}
				aux := hph.hashAuxBuffer[:0]
				if aux, err = hph.leafHashWithKeyVal(aux, hashedKeyBuf[:64-hashedKeyOffset+1], cell.Storage[:cell.StorageLen], true); err != nil {
					return nil, storageRootHashIsSet, nil, err
				}
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "leafHashWithKeyVal(singleton) storage hash [%x]\n", aux)
				}
				storageRootHash = *(*common.Hash)(aux[1:])
				storageRootHashIsSet = true
				cell.stateHashLen = 0
				hadToReset.Add(1)
			} else {
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "leafHashWithKeyVal for [%x]=>[%x] %v\n", hashedKeyBuf[:64-hashedKeyOffset+1], cell.Storage[:cell.StorageLen], cell.String())
				}
				leafHash, err := hph.leafHashWithKeyVal(buf, hashedKeyBuf[:64-hashedKeyOffset+1], cell.Storage[:cell.StorageLen], false)
				if err != nil {
					return nil, storageRootHashIsSet, nil, err
				}

				copy(cell.stateHash[:], leafHash[1:])
				cell.stateHashLen = int16(len(leafHash) - 1)
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "STATE HASH storage memoized %x spk %x\n", leafHash, cell.storageAddr[:cell.storageAddrLen])
				}

				return leafHash, storageRootHashIsSet, storageRootHash[:], nil
			}
		}
	}
	if cell.accountAddrLen > 0 {
		if err := hashKey(hph.keccak, cell.accountAddr[:cell.accountAddrLen], hashedKeyBuf[:], depth, hph.cellHashBuf[:]); err != nil {
			return nil, storageRootHashIsSet, nil, err
		}
		hashedKeyBuf[64-depth] = terminatorHexByte // Add terminator
		if !storageRootHashIsSet {
			if cell.extLen > 0 { // Extension
				if cell.hashLen == 0 {
					return nil, storageRootHashIsSet, nil, errors.New("computeCellHash extension without hash")
				}
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "extensionHash for [%x]=>[%x]\n", cell.extension[:cell.extLen], cell.hash[:cell.hashLen])
				}
				if storageRootHash, err = hph.extensionHash(cell.extension[:cell.extLen], cell.hash[:cell.hashLen]); err != nil {
					return nil, storageRootHashIsSet, nil, err
				}
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "EXTENSION HASH %x DROPS stateHash\n", storageRootHash)
				}
				cell.stateHashLen = 0
				hadToReset.Add(1)
				storageRootHashIsSet = true
			} else if cell.hashLen > 0 {
				storageRootHash = cell.hash
				storageRootHashIsSet = true
			} else {
				storageRootHash = empty.RootHash
			}
		}
		if !cell.loaded.account() {
			if cell.stateHashLen > 0 {
				res := append([]byte{160}, cell.stateHash[:cell.stateHashLen]...)
				hph.keccak.Reset()

				skippedLoad.Add(1)
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "REUSED stateHash %x apk %x\n", res, cell.accountAddr[:cell.accountAddrLen])
				}
				return res, storageRootHashIsSet, storageRootHash[:], nil
			}
			// storage root update or extension update could invalidate older stateHash, so we need to reload state
			hph.metrics.AccountLoad(cell.accountAddr[:cell.accountAddrLen])
			update, err := hph.accountFromCacheOrDB(cell.accountAddr[:cell.accountAddrLen])
			if err != nil {
				return nil, storageRootHashIsSet, storageRootHash[:], err
			}
			cell.setFromUpdate(update)
		}

		var valBuf [128]byte
		valLen := cell.accountForHashing(valBuf[:], storageRootHash)
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "accountLeafHashWithKey for [%x]=>[%x]\n", hashedKeyBuf[:65-depth], rlp.RlpEncodedBytes(valBuf[:valLen]))
		}
		leafHash, err := hph.accountLeafHashWithKey(buf, hashedKeyBuf[:65-depth], rlp.RlpEncodedBytes(valBuf[:valLen]))
		if err != nil {
			return nil, storageRootHashIsSet, nil, err
		}
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "STATE HASH account memoized %x\n", leafHash)
		}
		copy(cell.stateHash[:], leafHash[1:])
		cell.stateHashLen = int16(len(leafHash) - 1)
		return leafHash, storageRootHashIsSet, storageRootHash[:], nil
	}

	buf = append(buf, 0x80+32)
	if cell.extLen > 0 { // Extension
		if cell.hashLen > 0 {
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "extensionHash for [%x]=>[%x]\n", cell.extension[:cell.extLen], cell.hash[:cell.hashLen])
			}
			var hash common.Hash
			if hash, err = hph.extensionHash(cell.extension[:cell.extLen], cell.hash[:cell.hashLen]); err != nil {
				return nil, storageRootHashIsSet, storageRootHash[:], err
			}
			buf = append(buf, hash[:]...)
		} else {
			return nil, storageRootHashIsSet, storageRootHash[:], errors.New("computeCellHash extension without hash")
		}
	} else if cell.hashLen > 0 {
		buf = append(buf, cell.hash[:cell.hashLen]...)
	} else if storageRootHashIsSet {
		buf = append(buf, storageRootHash[:]...)
		copy(cell.hash[:], storageRootHash[:])
		cell.hashLen = int16(len(storageRootHash))
	} else {
		buf = append(buf, emptyRootHashBytes...)
	}
	return buf, storageRootHashIsSet, storageRootHash[:], nil
}

func (hph *HexPatriciaHashed) computeCellHash(cell *cell, depth int16, buf []byte) ([]byte, error) {
	var err error
	var storageRootHash common.Hash
	var storageRootHashIsSet bool
	if hph.memoizationOff {
		cell.stateHashLen = 0 // Reset stateHashLen to force recompute
	}
	if cell.storageAddrLen > 0 {
		var hashedKeyOffset int16
		if depth >= 64 {
			hashedKeyOffset = depth - 64
		}
		singleton := depth <= 64

		// Check cached stateHash BEFORE hashing key (optimization: skip key hash if using cache)
		if cell.stateHashLen > 0 {
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "REUSED stateHash %x spk %x\n", cell.stateHash[:cell.stateHashLen], cell.storageAddr[:cell.storageAddrLen])
			}
			skippedLoad.Add(1)
			if !singleton {
				return append(append(buf[:0], byte(160)), cell.stateHash[:cell.stateHashLen]...), nil
			}
			storageRootHashIsSet = true
			storageRootHash = *(*common.Hash)(cell.stateHash[:cell.stateHashLen])
		} else {
			koffset := hph.accountKeyLen
			if depth == 0 && cell.accountAddrLen == 0 {
				// if account key is empty, then we need to hash storage key from the key beginning
				koffset = 0
			}
			if err = cell.hashStorageKey(hph.keccak, koffset, 0, hashedKeyOffset, hph.cellHashBuf[:]); err != nil {
				return nil, err
			}
			cell.hashedExtension[64-hashedKeyOffset] = terminatorHexByte // Add terminator
			if !cell.loaded.storage() {
				return nil, fmt.Errorf("storage %x was not loaded as expected: cell %v", cell.storageAddr[:cell.storageAddrLen], cell.String())
				// update, err := hph.storageFromCacheOrDB(cell.storageAddr[:cell.storageAddrLen])
				// if err != nil {
				// 	return nil, err
				// }
				// cell.setFromUpdate(update)
			}

			leafHash, err := hph.leafHashWithKeyVal(buf, cell.hashedExtension[:64-hashedKeyOffset+1], cell.Storage[:cell.StorageLen], singleton)
			if err != nil {
				return nil, err
			}
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "leafHashWithKeyVal(singleton=%t) {%x} for [%x]=>[%x] %v\n",
					singleton, leafHash, cell.hashedExtension[:64-hashedKeyOffset+1], cell.Storage[:cell.StorageLen], cell.String())
			}
			if !singleton {
				copy(cell.stateHash[:], leafHash[1:])
				cell.stateHashLen = int16(len(leafHash) - 1)
				return leafHash, nil
			}
			storageRootHash = *(*common.Hash)(leafHash[1:])
			storageRootHashIsSet = true
			cell.stateHashLen = 0
			hadToReset.Add(1)
		}
	}
	if cell.accountAddrLen > 0 {
		if err := cell.hashAccKey(hph.keccak, depth, hph.cellHashBuf[:]); err != nil {
			return nil, err
		}
		cell.hashedExtension[64-depth] = terminatorHexByte // Add terminator
		if !storageRootHashIsSet {
			if cell.extLen > 0 { // Extension
				if cell.hashLen == 0 {
					return nil, errors.New("computeCellHash extension without hash")
				}
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "extensionHash for [%x]=>[%x]\n", cell.extension[:cell.extLen], cell.hash[:cell.hashLen])
				}
				if storageRootHash, err = hph.extensionHash(cell.extension[:cell.extLen], cell.hash[:cell.hashLen]); err != nil {
					return nil, err
				}
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "EXTENSION HASH %x DROPS stateHash\n", storageRootHash)
				}
				cell.stateHashLen = 0
				hadToReset.Add(1)
			} else if cell.hashLen > 0 {
				storageRootHash = cell.hash
			} else {
				storageRootHash = empty.RootHash
			}
		}
		if !cell.loaded.account() {
			if cell.stateHashLen > 0 {
				hph.keccak.Reset()

				skippedLoad.Add(1)
				if hph.traceW != nil {
					fmt.Fprintf(hph.traceW, "REUSED stateHash %x apk %x\n", cell.stateHash[:cell.stateHashLen], cell.accountAddr[:cell.accountAddrLen])
				}
				return append(append(buf[:0], byte(160)), cell.stateHash[:cell.stateHashLen]...), nil
			}
			// storage root update or extension update could invalidate older stateHash, so we need to reload state
			hph.metrics.AccountLoad(cell.accountAddr[:cell.accountAddrLen])
			update, err := hph.accountFromCacheOrDB(cell.accountAddr[:cell.accountAddrLen])
			if err != nil {
				return nil, err
			}
			cell.setFromUpdate(update)
		}

		valLen := cell.accountForHashing(hph.accValBuf, storageRootHash)
		buf, err = hph.accountLeafHashWithKey(buf, cell.hashedExtension[:65-depth], hph.accValBuf[:valLen])
		if err != nil {
			return nil, err
		}
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "accountLeafHashWithKey {%x} (memorised) for [%x]=>[%x]\n", buf, cell.hashedExtension[:65-depth], hph.accValBuf[:valLen])
		}
		copy(cell.stateHash[:], buf[1:])
		cell.stateHashLen = int16(len(buf)) - 1
		return buf, nil
	}

	buf = append(buf, 0x80+32)
	if cell.extLen > 0 { // Extension
		if cell.hashLen > 0 {
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "extensionHash for [%x]=>[%x]\n", cell.extension[:cell.extLen], cell.hash[:cell.hashLen])
			}
			if storageRootHash, err = hph.extensionHash(cell.extension[:cell.extLen], cell.hash[:cell.hashLen]); err != nil {
				return nil, err
			}
			buf = append(buf, storageRootHash[:]...)
		} else {
			return nil, errors.New("computeCellHash extension without hash")
		}
	} else if cell.hashLen > 0 {
		buf = append(buf, cell.hash[:cell.hashLen]...)
	} else if storageRootHashIsSet {
		buf = append(buf, storageRootHash[:]...)
		copy(cell.hash[:], storageRootHash[:])
		cell.hashLen = int16(len(storageRootHash))
	} else {
		buf = append(buf, emptyRootHashBytes...)
	}
	return buf, nil
}

// clampToAccountBoundary caps an unfold/extension length so a descent from above the
// 64-nibble account boundary stops exactly at it, where storage subtrees start.
func clampToAccountBoundary(depth, length int16) int16 {
	if depth < 64 && depth+length > 64 {
		return 64 - depth
	}
	return length
}

func (hph *HexPatriciaHashed) needUnfolding(hashedKey []byte) int16 {
	var cell *cell
	var depth int16
	if hph.activeRows == 0 {
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "needUnfolding root, rootChecked = %t\n", hph.rootChecked)
		}
		if hph.root.hashedExtLen == 64 && hph.root.accountAddrLen > 0 && hph.root.storageAddrLen > 0 {
			// in case if root is a leaf node with storage and account, we need to derive storage part of a key
			if err := hph.root.deriveHashedKeys(depth, hph.keccak, hph.accountKeyLen, hph.cellHashBuf[:]); err != nil {
				log.Warn("deriveHashedKeys for root with storage", "err", err, "cell", hph.root.FullString())
				return 0
			}
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "derived prefix %x\n", hph.currentKey[:hph.currentKeyLen])
			}
		}
		if hph.root.hashedExtLen == 0 && hph.root.hashLen == 0 {
			if hph.rootChecked {
				return 0 // Previously checked, empty root, no unfolding needed
			}
			return 1 // Need to attempt to unfold the root
		}
		cell = &hph.root
	} else {
		depth = hph.depths[hph.activeRows-1]
		// Guard before indexing: a probe key shorter than the row's depth (an unfold can
		// consume several extension nibbles at once) needs no further unfolding.
		if int16(len(hashedKey)) <= depth {
			return 0
		}
		nibble := int(hashedKey[hph.currentKeyLen])
		cell = &hph.grid[hph.activeRows-1][nibble]
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "currentKey [%x] needUnfolding cell (%d, %x, depth=%d) cell.hash=[%x]\n", hph.currentKey[:hph.currentKeyLen], hph.activeRows-1, nibble, depth, cell.hash[:cell.hashLen])
		}
	}
	if int16(len(hashedKey)) <= depth {
		return 0
	}
	if cell.hashedExtLen == 0 {
		if cell.hashLen == 0 { // cell is empty, no need to unfold further
			return 0
		}
		return 1 // unfold branch node
	}

	cpl := nibbles.CommonPrefixLen(hashedKey[depth:], cell.hashedExtension[:cell.hashedExtLen-1])
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "cpl=%d cell.hashedExtension=[%x] hashedKey[depth=%d:]=[%x]\n", cpl, cell.hashedExtension[:cell.hashedExtLen], depth, hashedKey[depth:])
	}
	unfolding := clampToAccountBoundary(depth, int16(cpl+1))
	if hph.traceW != nil && unfolding != int16(cpl+1) {
		fmt.Fprintf(hph.traceW, "adjusted unfolding=%d <- %d\n", unfolding, cpl+1)
	}
	return unfolding
}

func (c *cell) IsEmpty() bool {
	return c == nil || (c.hashLen == 0 && c.hashedExtLen == 0 && c.extLen == 0 && c.accountAddrLen == 0 && c.storageAddrLen == 0)
}

func (c *cell) String() string {
	var s strings.Builder
	s.WriteString("(")
	if c.hashLen > 0 {
		s.WriteString(fmt.Sprintf("hash(len=%d)=%x, ", c.hashLen, c.hash))
	}
	if c.hashedExtLen > 0 {
		s.WriteString(fmt.Sprintf("hashedExtension(len=%d)=%x, ", c.hashedExtLen, c.hashedExtension[:c.hashedExtLen]))
	}
	if c.extLen > 0 {
		s.WriteString(fmt.Sprintf("extension(len=%d)=%x, ", c.extLen, c.extension[:c.extLen]))
	}
	if c.accountAddrLen > 0 {
		s.WriteString(fmt.Sprintf("accountAddr=%x, ", c.accountAddr))
	}
	if c.storageAddrLen > 0 {
		s.WriteString(fmt.Sprintf("storageAddr=%x, ", c.storageAddr))
	}

	s.WriteString(")")
	return s.String()
}

func (hph *HexPatriciaHashed) PrintGrid() {
	fmt.Printf("GRID:\n")
	for row := 0; row < hph.activeRows; row++ {
		fmt.Printf("row %d depth %d:\n", row, hph.depths[row])
		for col := range 16 {
			cell := &hph.grid[row][col]
			if cell.hashedExtLen > 0 || cell.accountAddrLen > 0 {
				var cellHash []byte
				cellHash, _, _, err := hph.witnessComputeCellHashWithStorage(cell, hph.depths[row], nil)
				if err != nil {
					panic("failed to compute cell hash")
				}
				fmt.Printf("\t %x: %v cellHash=%x, \n", col, cell, cellHash)
			} else {
				fmt.Printf("\t %x: %v , \n", col, cell)
			}
		}
		fmt.Printf("\n")
	}
	fmt.Printf("\n")
}

// witnessMaterializeBranch decodes the branch stored at the given hashed-nibble prefix into
// a trie.FullNode whose children are HashNodes. Mirrors unfoldBranchNode's branch decode.
func (hph *HexPatriciaHashed) witnessMaterializeBranch(branchPrefix []byte, childDepth int16) (*trie.FullNode, error) {
	compact := nibbles.HexToCompact(branchPrefix)
	branchData, err := hph.readBranchAndCheckForFlushing(compact)
	if err != nil {
		return nil, err
	}
	if len(branchData) < 2 {
		return nil, fmt.Errorf("[witness] empty branch data at prefix %x", branchPrefix)
	}
	branchData = branchData[2:] // skip touch map
	bitmap := binary.BigEndian.Uint16(branchData[0:])
	pos := 2
	fullNode := &trie.FullNode{}
	for bitset := bitmap; bitset != 0; {
		bit := bitset & -bitset
		nibble := bits.TrailingZeros16(bit)
		var c cell
		fieldBits := branchData[pos]
		pos++
		if pos, err = c.fillFromFields(branchData, pos, cellFields(fieldBits)); err != nil {
			return nil, fmt.Errorf("[witness] fillFromFields at prefix %x: %w", branchPrefix, err)
		}
		if childDepth > 64 {
			c.accountAddrLen = 0
		}
		if err = c.deriveHashedKeys(childDepth, hph.keccak, hph.accountKeyLen, hph.cellHashBuf[:]); err != nil {
			return nil, err
		}
		cellHash, _, _, err := hph.witnessComputeCellHashWithStorage(&c, childDepth, nil)
		if err != nil {
			return nil, err
		}
		if len(cellHash) == length.Hash+1 { // strip the 0xa0 prefix
			cellHash = cellHash[1:]
		}
		fullNode.Children[nibble] = trie.NewHashNode(bytes.Clone(cellHash))
		bitset ^= bit
	}
	return fullNode, nil
}

// witnessMaterializeBranchChild decodes the branch at branchPrefix and verifies it
// hashes to wantHash, returning a node a strict verifier can descend instead of a bare
// HashNode. Same hash, so the witness root is unchanged.
func (hph *HexPatriciaHashed) witnessMaterializeBranchChild(branchPrefix []byte, childDepth int16, wantHash []byte) (*trie.FullNode, error) {
	branchNode, err := hph.witnessMaterializeBranch(branchPrefix, childDepth)
	if err != nil {
		return nil, err
	}
	subRoot := trie.NewInMemoryTrie(branchNode).Hash()
	if !bytes.Equal(subRoot[:], wantHash) {
		return nil, fmt.Errorf("[witness] materialized branch root mismatch at prefix %x: got %x want %x", branchPrefix, subRoot, wantHash)
	}
	return branchNode, nil
}

// readBranchAndCheckForFlushing reads a branch from ctx, flushing deferred updates first if the prefix is pending.
// This ensures we read fresh data when a prefix has been modified but not yet written.
func (hph *HexPatriciaHashed) readBranchAndCheckForFlushing(prefix []byte) ([]byte, error) {
	be := hph.branchEncoder
	if be.DeferUpdatesEnabled() && be.HasPendingPrefix(prefix) {
		if err := be.ApplyDeferredUpdates(16, hph.ctx.PutBranch); err != nil {
			return nil, err
		}
		be.ClearDeferred()
	}
	return hph.branchFromCacheOrDB(prefix)
}

// unfoldBranchNode returns true if unfolding has been done
func (hph *HexPatriciaHashed) unfoldBranchNode(row int, depth int16, deleted bool) error {
	key := nibbles.HexToCompact(hph.currentKey[:hph.currentKeyLen])
	hph.metrics.BranchLoad(hph.currentKey[:hph.currentKeyLen])

	branchData, err := hph.readBranchAndCheckForFlushing(key)
	if err != nil {
		return err
	}

	// depthsToTxNum is used for per-file metrics; step is no longer available
	// from the cache-or-DB helper (cache never had a meaningful step anyway).
	hph.depthsToTxNum[depth] = 0

	if len(branchData) >= 2 {
		branchData = branchData[2:] // skip touch map and keep the rest
	}
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "unfoldBranchNode prefix '%x', nibbles [%x] depth %d row %d '%x'\n",
			key, hph.currentKey[:hph.currentKeyLen], depth, row, branchData)
	}
	if !hph.rootChecked && hph.currentKeyLen == 0 && len(branchData) == 0 {
		// Special case - empty or deleted root
		hph.rootChecked = true
		return nil
	}
	if len(branchData) == 0 {
		log.Warn("got empty branch data during unfold", "key", hex.EncodeToString(key), "row", row, "depth", depth, "deleted", deleted)
		if hph.traceW != nil {
			branchData, _ = hph.branchFromCacheOrDB(key)
			fmt.Fprintf(hph.traceW, "unfoldBranchNode prefix '%x', nibbles [%x] depth %d row %d '%x' %s\n", key, hph.currentKey[:hph.currentKeyLen], depth, row, branchData, BranchData(branchData).String())
		}
		return fmt.Errorf("empty branch data read during unfold, compact prefix %x nibbles %x", key, hph.currentKey[:hph.currentKeyLen])
	}
	hph.branchBefore[row] = true
	if err := hph.decodeBranchIntoRow(row, depth, branchData, deleted); err != nil {
		return fmt.Errorf("prefix [%x] branchData[%x]: %w", hph.currentKey[:hph.currentKeyLen], branchData, err)
	}
	hph.depths[hph.activeRows] = depth
	hph.activeRows++
	return nil
}

// decodeBranchIntoRow decodes branch (with the touch/after-map prefix already stripped)
// into grid[row], records its touch/after maps, and derives hashed keys for present cells
// at depth. The on-disk read and its flush/metrics handling stay with each caller.
func (hph *HexPatriciaHashed) decodeBranchIntoRow(row int, depth int16, branch []byte, deleted bool) error {
	maps, err := DecodeBranchInto(branch, deleted, &hph.grid[row])
	if err != nil {
		return err
	}
	hph.touchMap[row] = maps.TouchMap
	hph.afterMap[row] = maps.AfterMap
	for bitset := maps.Bitmap; bitset != 0; {
		bit := bitset & -bitset
		nibble := bits.TrailingZeros16(bit)
		cell := &hph.grid[row][nibble]
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "cell (%d, %x, depth=%d) %s\n", row, nibble, depth, cell.FullString())
		}
		if err := cell.deriveHashedKeys(depth, hph.keccak, hph.accountKeyLen, hph.cellHashBuf[:]); err != nil {
			return err
		}
		bitset ^= bit
	}
	return nil
}

func (hph *HexPatriciaHashed) unfold(hashedKey []byte, unfolding int16) error {
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "unfold %d: activeRows: %d\n", unfolding, hph.activeRows)
	}
	var upCell *cell
	var touched, present bool
	var upDepth, depth int16
	if hph.activeRows == 0 {
		if hph.rootChecked && hph.root.hashLen == 0 && hph.root.hashedExtLen == 0 {
			return nil // No unfolding for empty root
		}
		upCell = &hph.root
		touched = hph.rootTouched
		present = hph.rootPresent
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "unfold root: touched: %t present: %t %s\n", touched, present, upCell.FullString())
		}
	} else {
		upRow := hph.activeRows - 1
		upDepth = hph.depths[upRow]
		upNibble := hashedKey[upDepth-1]
		upCell = &hph.grid[upRow][upNibble]

		touched = hph.touchMap[upRow]&(uint16(1)<<upNibble) != 0
		present = hph.afterMap[upRow]&(uint16(1)<<upNibble) != 0
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "upCell (%d, %x, updepth=%d) touched: %t present: %t\n", upRow, upNibble, upDepth, touched, present)
		}
		hph.currentKey[hph.currentKeyLen] = upNibble
		hph.currentKeyLen++
	}
	row := hph.activeRows
	for i := range 16 {
		hph.grid[row][i].reset()
	}
	hph.touchMap[row], hph.afterMap[row], hph.branchBefore[row] = 0, 0, false

	if upCell.hashedExtLen == 0 {
		depth = upDepth + 1
		return hph.unfoldBranchNode(row, depth, touched && !present)
	}

	lowest := min(unfolding, upCell.hashedExtLen)
	depth = upDepth + lowest
	copyLen := lowest - 1
	nibble := upCell.hashedExtension[copyLen]

	if touched {
		hph.touchMap[row] = uint16(1) << nibble
	}
	if present {
		hph.afterMap[row] = uint16(1) << nibble
	}

	cell := &hph.grid[row][nibble]
	cell.fillFromUpperCell(upCell, depth, lowest)
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "unfolded cell (%d, %x, depth=%d) %s\n", row, nibble, depth, cell.FullString())
	}
	if row >= 64 {
		cell.accountAddrLen = 0
	}

	if copyLen > 0 {
		copy(hph.currentKey[hph.currentKeyLen:], upCell.hashedExtension[:copyLen])
		hph.currentKeyLen += copyLen
	}

	hph.depths[hph.activeRows] = depth
	hph.activeRows++
	return nil
}

func (hph *HexPatriciaHashed) needFolding(hashedKey []byte) bool {
	return !bytes.HasPrefix(hashedKey, hph.currentKey[:hph.currentKeyLen])
}

// Process-cumulative trie-compute counters feeding the KVReadLevelledMetrics
// "skip ratio"/"reset ratio" Debug log at the end of ComputeCommitment.
var (
	hadToLoad   atomic.Uint64
	skippedLoad atomic.Uint64
	hadToReset  atomic.Uint64
)

var (
	rateFlushMu       sync.Mutex
	loadRatePublished uint64
	skipRatePublished uint64
)

// flushTrieStateRates publishes the cumulative load/skip atomics to their
// prometheus counters once per Process, replacing the per-key .Inc() on the hot
// path. The atomics are monotonic and the publish is delta-based, so the emitted
// counter value is identical regardless of how often this is called.
func flushTrieStateRates() {
	rateFlushMu.Lock()
	defer rateFlushMu.Unlock()
	if l := hadToLoad.Load(); l > loadRatePublished {
		mxTrieStateLoadRate.AddUint64(l - loadRatePublished)
		loadRatePublished = l
	}
	if s := skippedLoad.Load(); s > skipRatePublished {
		mxTrieStateSkipRate.AddUint64(s - skipRatePublished)
		skipRatePublished = s
	}
}

type skipStat struct {
	accLoaded, accSkipped, accReset, storReset, storLoaded, storSkipped uint64
}

const terminatorHexByte = 16 // max nibble value +1. Defines end of nibble line in the trie or splits address and storage space in trie.

// updateKind is a type of update that is being applied to the trie structure.
type updateKind uint8

const (
	// updateKindDelete means after we processed longest common prefix, row ended up empty.
	updateKindDelete updateKind = 0b0

	// updateKindPropagate is an update operation ended up with a single nibble which is leaf or extension node.
	// We do not store keys with only one cell as a value in db, instead we copy them upwards to the parent branch.
	//
	// In case current prefix existed before and node is fused to upper level, this causes deletion for current prefix
	// and update of branch value on upper level.
	// 	e.g.: leaf was at prefix 0xbeef, but we fuse it in level above, so
	//  - delete 0xbeef
	//  - update 0xbee
	updateKindPropagate updateKind = 0b01

	// updateKindBranch is an update operation ended up as a branch of 2+ cells.
	// That does not necessarily means that branch is NEW, it could be an existing branch that was updated.
	updateKindBranch updateKind = 0b10
)

// Kind defines how exactly given update should be folded upwards to the parent branch or root.
// It also returns number of nibbles that left in branch after the operation.
func afterMapUpdateKind(afterMap uint16) (kind updateKind, nibblesAfterUpdate int) {
	nibblesAfterUpdate = bits.OnesCount16(afterMap)
	switch nibblesAfterUpdate {
	case 0:
		return updateKindDelete, nibblesAfterUpdate
	case 1:
		return updateKindPropagate, nibblesAfterUpdate
	default:
		return updateKindBranch, nibblesAfterUpdate
	}
}

// foldBranch handles the updateKindBranch case: branch of 2+ cells.
func (hph *HexPatriciaHashed) foldBranch(row int, nibble, upDepth, depth int16, upCell *cell, updateKey []byte) error {
	if hph.touchMap[row] != 0 { // any modifications
		if row == 0 {
			hph.rootTouched = true
			hph.rootPresent = true
		} else {
			// Modification is propagated upwards
			hph.touchMap[row-1] |= uint16(1) << nibble
		}
	}
	bitmap := hph.touchMap[row] & hph.afterMap[row]
	if !hph.branchBefore[row] {
		// There was no branch node before, so we need to touch even the singular child that existed
		hph.touchMap[row] |= hph.afterMap[row]
		bitmap |= hph.afterMap[row]
	}

	// Calculate total length of all hashes
	nibblesLeftAfterUpdate := bits.OnesCount16(hph.afterMap[row])
	totalBranchLen, err := hph.prepareBranchCells(row, depth, nibblesLeftAfterUpdate)
	if err != nil {
		return err
	}

	hph.keccak2.Reset()
	pt := rlp.EncodeListPrefixToBuf(int(totalBranchLen), hph.hashAuxBuffer[:])
	if _, err := hph.keccak2.Write(hph.hashAuxBuffer[:pt]); err != nil {
		return err
	}
	hph.witness.beginBranch(hph.hashAuxBuffer[:pt])

	// Single pass: feed keccak2 + extract cellEncodeData
	cellData, err := hph.hashRow(row, depth)
	if err != nil {
		return err
	}

	if hph.branchEncoder.DeferUpdatesEnabled() {
		if err := hph.branchEncoder.CollectDeferredUpdate(hph.ctx, updateKey, bitmap, hph.touchMap[row], hph.afterMap[row], &cellData, !hph.branchBefore[row]); err != nil {
			return fmt.Errorf("failed to collect deferred branch update: %w", err)
		}
	} else {
		if err := hph.branchEncoder.CollectUpdate(hph.ctx, updateKey, bitmap, hph.touchMap[row], hph.afterMap[row], &cellData, !hph.branchBefore[row]); err != nil {
			return fmt.Errorf("failed to encode branch update: %w", err)
		}
	}
	upCell.extLen = depth - upDepth - 1
	upCell.hashedExtLen = upCell.extLen
	if upCell.extLen > 0 {
		copy(upCell.extension[:], hph.currentKey[upDepth:hph.currentKeyLen])
		copy(upCell.hashedExtension[:], hph.currentKey[upDepth:hph.currentKeyLen])
	}
	if depth < 64 {
		upCell.accountAddrLen = 0
	}
	upCell.storageAddrLen = 0
	upCell.hashLen = 32
	if _, err := hph.keccak2.Read(upCell.hash[:]); err != nil {
		return err
	}
	hph.witness.emitBranch(upCell.hash[:])
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "} [%x]\n", upCell.hash[:])
	}
	return nil
}

// feedBranchHashesToKeccak iterates all 17 branch slots, computing cell hashes
// for present cells via computeCellHash and writing 0x80 for empty slots into keccak2.
// Used by the deferred encoding path where keccak2 feeding is done before encoding.
func (hph *HexPatriciaHashed) feedBranchHashesToKeccak(row int, depth int16, emptyByte []byte) error {
	for bitset, lastNib := hph.afterMap[row], 0; ; {
		if bitset == 0 {
			// Write remaining empty cells to keccak2
			for i := lastNib; i < 17; i++ {
				if _, err := hph.keccak2.Write(emptyByte); err != nil {
					return err
				}
			}
			break
		}
		bit := bitset & -bitset
		nibble := bits.TrailingZeros16(bit)
		// Write empty cells before this nibble
		for i := lastNib; i < nibble; i++ {
			if _, err := hph.keccak2.Write(emptyByte); err != nil {
				return err
			}
		}
		lastNib = nibble + 1

		cell := &hph.grid[row][nibble]
		cellHash, err := hph.computeCellHash(cell, depth, hph.hashAuxBuffer[:0])
		if err != nil {
			return err
		}
		if _, err := hph.keccak2.Write(cellHash); err != nil {
			return err
		}
		bitset ^= bit
	}
	return nil
}

// hashRow performs a single pass over all 17 branch slots (16 nibbles + terminator),
// feeding cell hashes to keccak2 for present cells (per afterMap) and writing 0x80
// for empty slots. It simultaneously extracts cellEncodeData for each present cell.
// This replaces the separate feedBranchHashesToKeccak + cellEncodeData extraction loop.
func (hph *HexPatriciaHashed) hashRow(row int, depth int16) ([16]cellEncodeData, error) {
	var cellData [16]cellEncodeData
	b := [...]byte{0x80}
	capture := hph.witness.active()

	for bitset, lastNib := hph.afterMap[row], 0; ; {
		if bitset == 0 {
			// Write remaining empty cells to keccak2 (up to slot 16 inclusive = terminator)
			for i := lastNib; i < 17; i++ {
				if _, err := hph.keccak2.Write(b[:]); err != nil {
					return cellData, err
				}
				if capture {
					hph.witness.writeBranch(b[:])
				}
			}
			break
		}
		bit := bitset & -bitset
		nibble := bits.TrailingZeros16(bit)

		// Write empty cells before this nibble
		for i := lastNib; i < nibble; i++ {
			if _, err := hph.keccak2.Write(b[:]); err != nil {
				return cellData, err
			}
			if capture {
				hph.witness.writeBranch(b[:])
			}
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "  %x: empty(%d, %x, depth=%d)\n", i, row, i, depth)
			}
		}
		lastNib = nibble + 1

		cell := &hph.grid[row][nibble]

		// Warn about unloaded state
		if cell.accountAddrLen > 0 && cell.stateHashLen == 0 && !cell.loaded.account() && !cell.Deleted() {
			log.Warn("account not loaded", "row", row, "nibble", fmt.Sprintf("%x", nibble), "depth", depth, "cell", cell.String())
		}
		if cell.storageAddrLen > 0 && cell.stateHashLen == 0 && !cell.loaded.storage() && !cell.Deleted() {
			log.Warn("storage not loaded", "row", row, "nibble", fmt.Sprintf("%x", nibble), "depth", depth, "cell", cell.String())
		}

		// Save hash before compute for metrics
		var hashBefore []byte
		if dbg.KVReadLevelledMetrics && (cell.accountAddrLen > 0 || cell.storageAddrLen > 0) {
			hashBefore = make([]byte, cell.stateHashLen)
			copy(hashBefore, cell.stateHash[:cell.stateHashLen])
		}
		loadedBefore := cell.loaded

		cellHash, err := hph.computeCellHash(cell, depth, hph.hashAuxBuffer[:0])
		if err != nil {
			return cellData, err
		}
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "  %x: computeCellHash(%d, %x, depth=%d)=[%x]\n", nibble, row, nibble, depth, cellHash)
		}

		// Collect metrics on hash recomputation vs skip
		if dbg.KVReadLevelledMetrics && hashBefore != nil {
			counters := hph.hadToLoadL[hph.depthsToTxNum[depth]]
			if !bytes.Equal(hashBefore, cell.stateHash[:cell.stateHashLen]) {
				if cell.accountAddrLen > 0 {
					counters.accReset++
					counters.accLoaded++
				}
				if cell.storageAddrLen > 0 {
					counters.storReset++
					counters.storLoaded++
				}
			} else {
				if cell.accountAddrLen > 0 && !loadedBefore.account() && !cell.loaded.account() {
					counters.accSkipped++
				}
				if cell.storageAddrLen > 0 && !loadedBefore.storage() && !cell.loaded.storage() {
					counters.storSkipped++
				}
			}
			hph.hadToLoadL[hph.depthsToTxNum[depth]] = counters
		}

		if _, err := hph.keccak2.Write(cellHash); err != nil {
			return cellData, err
		}
		if capture {
			hph.witness.writeBranch(cellHash)
		}

		// Extract encoding data
		cellData[nibble] = cellEncodeDataFromCell(cell)

		bitset ^= bit
	}
	return cellData, nil
}

// prepareBranchCells iterates afterMap cells, drops stale memoized hashes,
// loads state from DB where needed, and returns the total RLP-encoded branch length.
func (hph *HexPatriciaHashed) prepareBranchCells(row int, depth int16, nibblesLeftAfterUpdate int) (int16, error) {
	totalBranchLen := int16(17 - nibblesLeftAfterUpdate) // For every empty cell, one byte
	for bitset, j := hph.afterMap[row], 0; bitset != 0; j++ {
		bit := bitset & -bitset
		nibble := bits.TrailingZeros16(bit)
		cell := &hph.grid[row][nibble]

		if hph.memoizationOff {
			cell.stateHashLen = 0
		}
		/* memoization of state hashes*/
		var counters skipStat
		if dbg.KVReadLevelledMetrics {
			counters = hph.hadToLoadL[hph.depthsToTxNum[depth]]
		}
		if cell.stateHashLen > 0 && (hph.touchMap[row]&hph.afterMap[row]&uint16(1<<nibble) > 0 || cell.stateHashLen != length.Hash) {
			// drop state hash if updated or hashLen < 32 (corner case, may even not encode such leaf hashes)
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "DROP hash for (%d, %x, depth=%d) %s\n", row, nibble, depth, cell.FullString())
			}
			cell.stateHashLen = 0
			hadToReset.Add(1)
			if cell.accountAddrLen > 0 {
				counters.accReset++
			}
			if cell.storageAddrLen > 0 {
				counters.storReset++
			}
		}
		var err error
		counters, err = hph.loadStateIfNeeded(cell, counters)
		if err != nil {
			return 0, err
		}
		if dbg.KVReadLevelledMetrics {
			hph.hadToLoadL[hph.depthsToTxNum[depth]] = counters
		}
		/* end of memoization */

		totalBranchLen += hph.computeCellHashLen(cell, depth)
		bitset ^= bit
	}
	return totalBranchLen, nil
}

// foldPropagate handles the updateKindPropagate case: leaf or extension node.
func (hph *HexPatriciaHashed) foldPropagate(row int, nibble, upDepth, depth int16, upCell *cell, updateKey []byte) error {
	if hph.touchMap[row] != 0 {
		// any modifications
		if row == 0 {
			hph.rootTouched = true
			// A propagate fold leaves exactly one survivor, so the root exists; without this
			// the next unfold reads touched && !present and deletes the whole subtree.
			hph.rootPresent = true
		} else {
			// Modification is propagated upwards
			hph.touchMap[row-1] |= uint16(1) << nibble
		}
	}
	childNibble := bits.TrailingZeros16(hph.afterMap[row])
	cell := &hph.grid[row][childNibble]
	upCell.extLen = 0
	upCell.stateHashLen = 0
	var counters skipStat
	if dbg.KVReadLevelledMetrics {
		counters = hph.hadToLoadL[hph.depthsToTxNum[depth]]
	}
	counters, err := hph.loadStateIfNeeded(cell, counters)
	if err != nil {
		return err
	}
	if dbg.KVReadLevelledMetrics {
		hph.hadToLoadL[hph.depthsToTxNum[depth]] = counters
	}
	// propagate cell into parent row
	upCell.fillFromLowerCell(cell, depth, hph.currentKey[upDepth:hph.currentKeyLen], childNibble)

	if err := hph.collectDeleteUpdate(updateKey, row, true); err != nil {
		return err
	}
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "formed leaf (%d %x, depth=%d) [%x] %s\n", row, childNibble, depth, updateKey, cell.FullString())
	}
	return nil
}

// foldDelete handles the updateKindDelete case: everything at this row was deleted.
func (hph *HexPatriciaHashed) foldDelete(row int, nibble, upDepth int16, upCell *cell, updateKey []byte) error {
	if hph.touchMap[row] != 0 {
		if row == 0 {
			// Root is deleted because the tree is empty
			hph.rootTouched = true
			hph.rootPresent = false
		} else if upDepth == 64 {
			// Special case - all storage items of an account have been deleted, but it does not automatically delete the account, just makes it empty storage
			// Therefore we are not propagating deletion upwards, but turn it into a modification
			hph.touchMap[row-1] |= uint16(1) << nibble
		} else {
			// Deletion is propagated upwards
			hph.touchMap[row-1] |= uint16(1) << nibble
			hph.afterMap[row-1] &^= uint16(1) << nibble
			if hph.collapseTracer != nil && bits.OnesCount16(hph.afterMap[row-1]) == 1 {
				hph.detectCascadingCollapseAtRow(row - 1)
			}
		}
	}

	upCell.reset()
	return hph.collectDeleteUpdate(updateKey, row, true)
}

// collectDeleteUpdate encodes a branch deletion if a branch existed before at this row.
// If evictCache is true, it also evicts the branch from the cache.
func (hph *HexPatriciaHashed) collectDeleteUpdate(updateKey []byte, row int, evictCache bool) error {
	if hph.branchBefore[row] {
		if err := hph.branchEncoder.CollectUpdate(hph.ctx, updateKey, 0, hph.touchMap[row], 0, nil, false); err != nil {
			return fmt.Errorf("failed to encode leaf node update: %w", err)
		}
	}
	return nil
}

// The purpose of fold is to reduce hph.currentKey[:hph.currentKeyLen]. It should be invoked
// until that current key becomes a prefix of hashedKey that we will process next
// (in other words until the needFolding function returns 0)
func (hph *HexPatriciaHashed) fold() error {
	updateKeyLen := hph.currentKeyLen
	if hph.activeRows == 0 {
		return errors.New("cannot fold - no active rows")
	}
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "fold [%x] activeRows: %d touchMap: %016b afterMap: %016b\n", hph.currentKey[:hph.currentKeyLen], hph.activeRows, hph.touchMap[hph.activeRows-1], hph.afterMap[hph.activeRows-1])
	}
	// Move information to the row above
	var upCell *cell
	var nibble, upDepth int16
	row := hph.activeRows - 1
	upRow := row - 1
	if row == 0 {
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "fold: parent is root %s\n", hph.root.FullString())
		}
		upCell = &hph.root
	} else {
		upDepth = hph.depths[upRow]
		nibble = int16(hph.currentKey[upDepth-1])
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "fold: parent (%d, %x, depth=%d)\n", upRow, nibble, upDepth)
		}
		upCell = &hph.grid[upRow][nibble]
	}

	depth := hph.depths[row]

	updateKey := nibbles.HexToCompact(hph.currentKey[:updateKeyLen])
	defer func() { hph.depthsToTxNum[depth] = 0 }()

	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "fold: (row=%d, {%s}, depth=%d) prefix [%x] touchMap: %016b afterMap: %016b \n",
			row, updatedNibs(hph.touchMap[row]&hph.afterMap[row]), depth, hph.currentKey[:hph.currentKeyLen], hph.touchMap[row], hph.afterMap[row])
	}

	updateKind, _ := afterMapUpdateKind(hph.afterMap[row])
	var err error
	switch updateKind {
	case updateKindDelete: // Everything deleted
		err = hph.foldDelete(row, nibble, upDepth, upCell, updateKey)
	case updateKindPropagate: // Leaf or extension node
		err = hph.foldPropagate(row, nibble, upDepth, depth, upCell, updateKey)
	case updateKindBranch:
		err = hph.foldBranch(row, nibble, upDepth, depth, upCell, updateKey)
	}
	if err != nil {
		return err
	}

	hph.activeRows--
	hph.currentKeyLen = max(upDepth-1, 0)
	return nil
}

func (hph *HexPatriciaHashed) loadStateIfNeeded(cell *cell, counters skipStat) (skipStat, error) {
	if cell.stateHashLen == 0 {
		if !cell.loaded.account() && cell.accountAddrLen > 0 {
			hph.metrics.AccountLoad(cell.accountAddr[:cell.accountAddrLen])
			upd, err := hph.accountFromCacheOrDB(cell.accountAddr[:cell.accountAddrLen])
			if err != nil {
				return counters, err
			}
			cell.setFromUpdate(upd)
			// if the update is empty, the loaded flag was not updated so do it manually
			cell.loaded = cell.loaded.addFlag(cellLoadAccount)
			counters.accLoaded++
		}
		if !cell.loaded.storage() && cell.storageAddrLen > 0 {
			hph.metrics.StorageLoad(cell.storageAddr[:cell.storageAddrLen])
			upd, err := hph.storageFromCacheOrDB(cell.storageAddr[:cell.storageAddrLen])
			if err != nil {
				return counters, err
			}
			cell.setFromUpdate(upd)
			// if the update is empty, the loaded flag was not updated so do it manually
			cell.loaded = cell.loaded.addFlag(cellLoadStorage)
			counters.storLoaded++
		}
	}
	return counters, nil
}

func (hph *HexPatriciaHashed) deleteCell(hashedKey []byte) {
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "deleteCell, activeRows = %d\n", hph.activeRows)
	}
	var cell *cell
	if hph.activeRows == 0 { // Remove the root
		cell = &hph.root
		hph.rootTouched, hph.rootPresent = true, false
	} else {
		row := hph.activeRows - 1
		if hph.depths[row] < int16(len(hashedKey)) {
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "deleteCell skipping spurious delete depth=%d, len(hashedKey)=%d\n", hph.depths[row], len(hashedKey))
			}
			return
		}
		nibble := int(hashedKey[hph.currentKeyLen])
		cell = &hph.grid[row][nibble]
		col := uint16(1) << nibble
		if hph.afterMap[row]&col != 0 {
			// Prevent "spurious deletions", i.e. deletion of absent items
			hph.touchMap[row] |= col
			hph.afterMap[row] &^= col
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "deleteCell setting (%d, %x)\n", row, nibble)
			}
		} else {
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "deleteCell ignoring (%d, %x)\n", row, nibble)
			}
		}
	}
	cell.reset()
}

// detectCollapseBeforeDelete checks the row above the deepest row to see if
// deleting the current key will cause a node collapse. If that row has exactly
// 2 non-empty cells, one of them is on the delete path and the other is the
// sibling that will survive the collapse.
func (hph *HexPatriciaHashed) detectCollapseBeforeDelete(hashedKey []byte) {
	if hph.activeRows < 2 {
		return
	}
	parentRow := hph.activeRows - 2
	children := bits.OnesCount16(hph.afterMap[parentRow])
	if children != 2 {
		return
	}

	// collapse detected!
	compact := NibblesToString(hashedKey)
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "[collapse] updateCell: hashedKey=%s (len=%d nibbles), deleted=true, activeRows=%d\n",
			compact, len(hashedKey), hph.activeRows)
	}

	// Exactly 2 children in the parent row — one is on the delete path,
	// the other is the sibling that will survive the collapse.
	depth := hph.depths[parentRow] - 1
	deleteNibble := int(hashedKey[depth])

	// Find the sibling nibble (the other set bit in afterMap)
	siblingNibble := -1
	for i := range 16 {
		if hph.afterMap[parentRow]&(1<<i) != 0 && i != deleteNibble {
			siblingNibble = i
			break
		}
	}
	if siblingNibble < 0 {
		return
	}

	siblingCell := &hph.grid[parentRow][siblingNibble]

	// Build the sibling's full hashed key path
	siblingPath := make([]byte, int(depth)+1+int(siblingCell.hashedExtLen))
	copy(siblingPath, hph.currentKey[:depth])
	siblingPath[depth] = byte(siblingNibble)
	if siblingCell.hashedExtLen > 0 {
		copy(siblingPath[int(depth)+1:], siblingCell.hashedExtension[:siblingCell.hashedExtLen])
	}

	compactSibling := NibblesToString(siblingPath)
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "[collapse] found at parentRow=%d depth=%d: deleteNibble=%x, siblingNibble=%x, siblingPath=%s (len=%d), hashLen=%d, extLen=%d\n",
			parentRow, depth, deleteNibble, siblingNibble, compactSibling, len(siblingPath), siblingCell.hashLen, siblingCell.hashedExtLen)
	}
	hph.collapseTracer(siblingPath, bytes.Clone(hph.currentKey[:depth]))
}

// detectCascadingCollapseAtRow detects a FullNode→ShortNode collapse caused by
// a child deletion propagated upward from fold(). Called when afterMap[row]
// has exactly 1 remaining child after a nibble was cleared.
func (hph *HexPatriciaHashed) detectCascadingCollapseAtRow(row int) {
	depth := hph.depths[row] - 1
	survivingNibble := bits.TrailingZeros16(hph.afterMap[row])
	survivingCell := &hph.grid[row][survivingNibble]

	// Build the surviving child's full hashed key path
	siblingPath := make([]byte, int(depth)+1+int(survivingCell.hashedExtLen))
	copy(siblingPath, hph.currentKey[:depth])
	siblingPath[depth] = byte(survivingNibble)
	if survivingCell.hashedExtLen > 0 {
		copy(siblingPath[int(depth)+1:], survivingCell.hashedExtension[:survivingCell.hashedExtLen])
	}

	compactSibling := NibblesToString(siblingPath)
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "[cascade-collapse] found at row=%d depth=%d: survivingNibble=%x, siblingPath=%s (len=%d), hashLen=%d, hashedExtLen=%d\n",
			row, depth, survivingNibble, compactSibling, len(siblingPath), survivingCell.hashLen, survivingCell.hashedExtLen)
	}
	hph.collapseTracer(siblingPath, bytes.Clone(hph.currentKey[:depth]))
}

// fetches cell by key and set touch/after maps. Requires that prefix to be already unfolded
func (hph *HexPatriciaHashed) updateCell(plainKey, hashedKey []byte, u *Update) (cell *cell) {
	hph.metrics.Updates(plainKey)

	if u.Deleted() {
		// Before the delete, check if this will cause a node collapse (FullNode → single child).
		if hph.collapseTracer != nil && hph.activeRows > 0 {
			hph.detectCollapseBeforeDelete(hashedKey)
		}

		hph.deleteCell(hashedKey)
		hph.lastUpdateCellWasEmpty = false
		return nil
	}

	var depth int16
	if hph.activeRows == 0 {
		cell = &hph.root
		hph.rootTouched, hph.rootPresent = true, true
	} else {
		row := hph.activeRows - 1
		depth = hph.depths[row]
		nibble := int(hashedKey[hph.currentKeyLen])
		cell = &hph.grid[row][nibble]
		col := uint16(1) << nibble

		hph.touchMap[row] |= col
		hph.afterMap[row] |= col
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "updateCell setting (%d, %x, depth=%d)\n", row, nibble, depth)
		}
	}
	hph.lastUpdateCellWasEmpty = cell.IsEmpty()
	if cell.hashedExtLen == 0 {
		copy(cell.hashedExtension[:], hashedKey[depth:])
		cell.hashedExtLen = int16(len(hashedKey)) - depth
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "set downHasheKey=[%x]\n", cell.hashedExtension[:cell.hashedExtLen])
		}
	} else {
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "keep downHasheKey=[%x]\n", cell.hashedExtension[:cell.hashedExtLen])
		}
	}
	if int16(len(plainKey)) == hph.accountKeyLen {
		cell.accountAddrLen = int16(len(plainKey))
		copy(cell.accountAddr[:], plainKey)

		cell.CodeHash = empty.CodeHash
	} else { // set storage key
		cell.storageAddrLen = int16(len(plainKey))
		copy(cell.storageAddr[:], plainKey)
	}
	cell.stateHashLen = 0

	cell.setFromUpdate(u)
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "updateCell %x => %s\n", plainKey, u.String())
	}
	return cell
}

func (hph *HexPatriciaHashed) RootHash() ([]byte, error) {
	hph.root.stateHashLen = 0
	rootHash, err := hph.computeCellHash(&hph.root, 0, nil)
	if err != nil {
		return nil, err
	}
	return rootHash[1:], nil // first byte is 128+hash_len=160
}

// unfoldKeyPath drives hph.unfold in a loop until the trie is unfolded
// far enough that the cell at hashedKey can be updated — i.e. until
// needUnfolding returns 0. This is the per-key traversal primitive
// that follows the fold step in followAndUpdate.
//
// plainKey is used only for per-key metrics labelling (StartUnfolding).
// Pass an empty/nil slice if no metric attribution is needed.
func (hph *HexPatriciaHashed) unfoldKeyPath(hashedKey, plainKey []byte) error {
	for unfolding := hph.needUnfolding(hashedKey); unfolding > 0; unfolding = hph.needUnfolding(hashedKey) {
		printLater := hph.currentKeyLen == 0 && hph.mounted && hph.traceW != nil
		var unfoldDone func()
		if dbg.KVReadLevelledMetrics {
			unfoldDone = hph.metrics.StartUnfolding(plainKey)
		}
		if err := hph.unfold(hashedKey, unfolding); err != nil {
			return fmt.Errorf("unfold: %w", err)
		}
		if unfoldDone != nil {
			unfoldDone()
		}
		if printLater {
			fmt.Fprintf(hph.traceW, "[%x] subtrie pref '%x' d=%d\n", hph.mountedNib, hph.currentKey[:hph.currentKeyLen], hph.depths[max(0, hph.activeRows-1)])
		}
	}
	return nil
}

func (hph *HexPatriciaHashed) followAndUpdate(hashedKey, plainKey []byte, stateUpdate *Update) (err error) {
	//if hph.traceW != nil {
	// fmt.Fprintf(hph.traceW, "mnt: %0x current: %x path %x\n", hph.mountedNib, hph.currentKey[:hph.currentKeyLen], hashedKey)
	//}
	// Keep folding until the currentKey is the prefix of the key we modify
	for hph.needFolding(hashedKey) {
		var foldDone func()
		if dbg.KVReadLevelledMetrics {
			foldDone = hph.metrics.StartFolding(plainKey)
		}
		if err := hph.fold(); err != nil {
			return fmt.Errorf("fold: %w", err)
		}
		if foldDone != nil {
			foldDone()
		}
	}
	// Now unfold the path so the cell at hashedKey is reachable.
	if err := hph.unfoldKeyPath(hashedKey, plainKey); err != nil {
		return err
	}

	if stateUpdate == nil {
		// Update the cell
		if int16(len(plainKey)) == hph.accountKeyLen {
			hph.metrics.AccountLoad(plainKey)
			stateUpdate, err = hph.accountFromCacheOrDB(plainKey)
			if err != nil {
				return fmt.Errorf("GetAccount for key %x failed: %w", plainKey, err)
			}
		} else {
			hph.metrics.StorageLoad(plainKey)
			stateUpdate, err = hph.storageFromCacheOrDB(plainKey)
			if err != nil {
				return fmt.Errorf("GetStorage for key %x failed: %w", plainKey, err)
			}
		}
	}
	hph.updateCell(plainKey, hashedKey, stateUpdate)

	mxTrieProcessedKeys.Inc()
	return nil
}

func (hph *HexPatriciaHashed) foldMounted(ctx context.Context, nib int) (cell, error) {
	if nib != hph.mountedNib {
		panic(fmt.Sprintf("foldMounted: nib (%x)!= mountedNib (%x)", nib, hph.mountedNib))
	}

	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "====[%x] folding rows %d depths %+v\n", hph.mountedNib, hph.activeRows, hph.depths[:hph.activeRows])
		defer func() { fmt.Fprintf(hph.traceW, "=======[%x] folded =========\n", hph.mountedNib) }()
	}

	for hph.activeRows > 0 {
		if err := ctx.Err(); err != nil {
			return cell{}, err
		}
		// fmt.Printf("===[%x] folding prefix %x (len %d)\n", hph.mountedNib, hph.currentKey[:hph.currentKeyLen], hph.currentKeyLen)
		if hph.activeRows == 1 && hph.depths[hph.activeRows-1] == hph.mountWall {
			if hph.traceW != nil {
				fmt.Fprintf(hph.traceW, "mount early as nibble %02x %s\n", hph.mountedNib, hph.grid[0][hph.mountedNib].String())
			}
			// fmt.Printf("===[%x] stop folding at %x\n", hph.mountedNib, hph.currentKey[:hph.currentKeyLen])
			return hph.grid[0][hph.mountedNib], nil
		}
		if err := hph.fold(); err != nil {
			return cell{}, fmt.Errorf("final fold: %w", err)
		}
	}

	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "===[%x] !@folded to the root\n", hph.mountedNib)
	}
	if hph.rootPresent && hph.rootTouched {
		if hph.traceW != nil {
			fmt.Fprintf(hph.traceW, "mount root as %02x %s\n", hph.mountedNib, hph.root.String())
		}
		return hph.root, nil
	}
	return cell{}, fmt.Errorf("foldMounted[%x]: folded past the mount wall to an unrooted base; the base must be seeded with a wall row", hph.mountedNib)
}

// captureExtensionDivergence materializes the branch behind a folded extension the
// key diverges into (never unfolded during positioning) so a strict verifier can
// descend it; it is hash-verified, so a wrong prefix errors rather than corrupts.
func (hph *HexPatriciaHashed) captureExtensionDivergence(hashedKey []byte, set *witnessNodeSet) error {
	if hph.activeRows == 0 {
		return nil
	}
	row := hph.activeRows - 1
	depth := hph.depths[row]
	keyPos := hph.currentKeyLen
	if keyPos >= int16(len(hashedKey)) {
		return nil
	}
	cell := &hph.grid[row][hashedKey[keyPos]]
	if cell.hashedExtLen == 0 || cell.hashLen == 0 {
		return nil
	}
	extKeyLength := clampToAccountBoundary(depth, cell.hashedExtLen)
	hashedExtKey := cell.hashedExtension[:extKeyLength]
	endPos := min(keyPos+extKeyLength+1, int16(len(hashedKey)))
	fullPathLength := int(keyPos+1) + len(hashedExtKey)
	if bytes.Equal(hashedExtKey, hashedKey[keyPos+1:endPos]) || fullPathLength == 64 || fullPathLength == 128 {
		return nil
	}
	branchPrefix := make([]byte, 0, fullPathLength)
	branchPrefix = append(branchPrefix, hashedKey[:keyPos+1]...)
	branchPrefix = append(branchPrefix, hashedExtKey...)
	bn, err := hph.witnessMaterializeBranchChild(branchPrefix, int16(len(branchPrefix))+1, cell.hash[:cell.hashLen])
	if err != nil {
		return err
	}
	encoded, err := trie.NewInMemoryTrie(bn).RLPEncode()
	if err != nil {
		return err
	}
	if len(encoded) > 0 {
		set.onNode(encoded[0], cell.hash[:cell.hashLen])
	}
	return nil
}

// Witnesses builds the execution-witness node set on the fly during the fold,
// capturing consensus node bytes as they are hashed. It returns the captured superset
// (root first), the fold's hashed keys, and the root hash; callers prune to the lean set.
func (hph *HexPatriciaHashed) Witnesses(ctx context.Context, updates *Updates, produceExclusionProofs bool, logPrefix string) (nodes [][]byte, provedKeys [][]byte, rootHash []byte, err error) {
	hph.memoizationOff = true
	set := newWitnessNodeSet()
	hph.witness.tracer = set
	defer hph.witness.reset()

	provedKeys = make([][]byte, 0, updates.Size())
	err = updates.HashSort(ctx, nil, func(hashedKey, plainKey []byte, stateUpdate *Update) error {
		provedKeys = append(provedKeys, bytes.Clone(hashedKey))
		if len(plainKey) > 0 {
			if int16(len(plainKey)) == hph.accountKeyLen {
				if _, err := hph.accountFromCacheOrDB(plainKey); err != nil {
					return fmt.Errorf("account with plainkey=%x not found: %w", plainKey, err)
				}
			} else {
				if _, err := hph.storageFromCacheOrDB(plainKey); err != nil {
					return fmt.Errorf("storage with plainkey=%x not found: %w", plainKey, err)
				}
			}
		}

		for hph.needFolding(hashedKey) {
			if err := hph.fold(); err != nil {
				return fmt.Errorf("fold: %w", err)
			}
		}
		// fold back non-branch virtual rows left by previous extension splits
		for hph.activeRows > 0 && !hph.branchBefore[hph.activeRows-1] {
			if err := hph.fold(); err != nil {
				return fmt.Errorf("fold non-branch: %w", err)
			}
		}

		for hph.currentKeyLen < int16(len(hashedKey)) {
			unfolding := hph.needUnfolding(hashedKey)
			if unfolding <= 0 {
				break
			}
			if produceExclusionProofs {
				if err := hph.captureExtensionDivergence(hashedKey, set); err != nil {
					return fmt.Errorf("capture extension divergence: %w", err)
				}
			}
			if err := hph.unfold(hashedKey, unfolding); err != nil {
				return fmt.Errorf("unfold: %w", err)
			}
		}
		if hph.activeRows > 0 && !hph.branchBefore[hph.activeRows-1] &&
			hph.currentKeyLen < int16(len(hashedKey)) {
			divergeNibble := hashedKey[hph.currentKeyLen]
			if hph.grid[hph.activeRows-1][divergeNibble].IsEmpty() {
				if err := hph.fold(); err != nil {
					return fmt.Errorf("fold empty diverging row: %w", err)
				}
			}
		}

		if hph.activeRows > 0 && hph.currentKeyLen < int16(len(hashedKey)) {
			lastNibble := int(hashedKey[hph.currentKeyLen])
			lastCell := &hph.grid[hph.activeRows-1][lastNibble]
			if int16(len(hashedKey)) == hph.depths[hph.activeRows-1] && len(hashedKey) != 64 && len(hashedKey) != 128 && lastCell.hashLen > 0 {
				if err := hph.unfold(hashedKey, 1); err != nil {
					return fmt.Errorf("extra unfold: %w", err)
				}
			}
		}
		return nil
	})
	if err != nil {
		return nil, nil, nil, fmt.Errorf("hash sort failed: %w", err)
	}

	for hph.activeRows > 0 {
		if err := hph.fold(); err != nil {
			return nil, nil, nil, fmt.Errorf("final fold: %w", err)
		}
	}

	rootHash, err = hph.RootHash()
	if err != nil {
		return nil, nil, nil, fmt.Errorf("root hash evaluation failed: %w", err)
	}
	nodes, err = set.nodes(rootHash)
	if err != nil {
		return nil, nil, nil, err
	}
	return nodes, provedKeys, rootHash, nil
}

func (hph *HexPatriciaHashed) Process(ctx context.Context, updates *Updates, logPrefix string, onProgress func(*CommitProgress), warmup WarmupConfig) (rootHash []byte, err error) {
	var (
		m  runtime.MemStats
		ki uint64

		updatesCount = updates.Size()
		start        = time.Now()
		logEvery     = time.NewTicker(20 * time.Second)
	)

	//hph.traceW = os.Stderr

	hph.metrics.Reset()
	hph.metrics.updates.Store(updatesCount)
	if hph.metrics.collectCommitmentMetrics {
		defer func() {
			hph.metrics.TotalProcessingTimeInc(start)
			hph.metrics.WriteToCSV()
		}()
	}

	defer func() { logEvery.Stop() }()

	// Setup warmup if configured
	var warmuper *Warmuper
	if warmup.Enabled {
		warmuper = NewWarmuper(ctx, warmup)
		warmuper.Start()
		defer warmuper.CloseAndWait()
	}

	err = updates.HashSort(ctx, warmuper, func(hashedKey, plainKey []byte, stateUpdate *Update) error {
		select {
		case <-logEvery.C:
			if onProgress != nil {
				onProgress(&CommitProgress{
					KeyIndex:    ki,
					UpdateCount: updatesCount,
					Metrics:     hph.metrics.AsValues(),
				})
			} else {
				dbg.ReadMemStats(&m)
				keysPerSec := uint64(float64(ki) / time.Since(start).Seconds())
				log.Info(fmt.Sprintf("[%s][agg] computing trie", logPrefix),
					append(append([]any{"progress", fmt.Sprintf("%s/%s", common.PrettyCounter(ki), common.PrettyCounter(updatesCount)), "keys/s", common.PrettyCounter(keysPerSec)},
						hph.metrics.logMetrics()...), "alloc", common.ByteCount(m.Alloc), "sys", common.ByteCount(m.Sys))...)
			}
		default:
		}

		if hph.traceW != nil {
			update := stateUpdate

			if update == nil {
				if int16(len(plainKey)) == hph.accountKeyLen {
					update, err = hph.accountFromCacheOrDB(plainKey)
					if err != nil {
						return fmt.Errorf("GetAccount for key %x failed: %w", plainKey, err)
					}
				} else {
					update, err = hph.storageFromCacheOrDB(plainKey)
					if err != nil {
						return fmt.Errorf("GetStorage for key %x failed: %w", plainKey, err)
					}
				}
			}

			trace := fmt.Sprintf("(%d/%d) plainKey [%x] %s hashedKey [%x] currentKey [%x]", ki+1, updatesCount, plainKey, update, hashedKey, hph.currentKey[:hph.currentKeyLen])

			fmt.Fprintf(hph.traceW, "[proc] %s\n", trace)
			// fmt.Println(trace)
		}

		if err := hph.followAndUpdate(hashedKey, plainKey, stateUpdate); err != nil {
			return fmt.Errorf("followAndUpdate: %w", err)
		}
		ki++
		if onProgress != nil && ki == updatesCount {
			onProgress(&CommitProgress{
				KeyIndex:    ki,
				UpdateCount: updatesCount,
				Metrics:     hph.metrics.AsValues(),
			})
		}
		return nil
	})
	if err != nil {
		return nil, fmt.Errorf("hash sort failed: %w", err)
	}

	// Folding everything up to the root
	for hph.activeRows > 0 {
		var foldDone func()
		if dbg.KVReadLevelledMetrics {
			foldDone = hph.metrics.StartFolding(nil)
		}
		if err = hph.fold(); err != nil {
			return nil, fmt.Errorf("final fold: %w", err)
		}
		if foldDone != nil {
			foldDone()
		}
	}

	rootHash, err = hph.RootHash()
	if err != nil {
		return nil, fmt.Errorf("root hash evaluation failed: %w", err)
	}
	if hph.traceW != nil {
		fmt.Fprintf(hph.traceW, "root hash %x updates %d\n", rootHash, updatesCount)
	}
	if warmuper != nil {
		warmuper.DrainPending()
	}

	if hph.branchEncoder.DeferUpdatesEnabled() && !hph.leaveDeferredForCaller {
		if err = hph.branchEncoder.ApplyDeferredUpdates(runtime.NumCPU(), hph.ctx.PutBranch); err != nil {
			return nil, fmt.Errorf("apply deferred updates: %w", err)
		}
		hph.branchEncoder.ClearDeferred()
	}

	flushTrieStateRates()

	if dbg.KVReadLevelledMetrics {
		hph.metrics.CollectFileDepthStats(hph.hadToLoadL)
		log.Debug("commitment finished, counters updated (no reset)",
			//"hadToLoad", common.PrettyCounter(hadToLoad.Load()), "skippedLoad", common.PrettyCounter(skippedLoad.Load()),
			//"hadToReset", common.PrettyCounter(hadToReset.Load()),
			"skip ratio", fmt.Sprintf("%.1f%%", 100*(float64(skippedLoad.Load())/float64(hadToLoad.Load()+skippedLoad.Load()))),
			"reset ratio", fmt.Sprintf("%.1f%%", 100*(float64(hadToReset.Load())/float64(hadToLoad.Load()))),
			"keys", common.PrettyCounter(ki), "spent", time.Since(start),
		)
		ends := make([]uint64, 0, len(hph.hadToLoadL))
		for k := range hph.hadToLoadL {
			ends = append(ends, k)
		}
		slices.SortFunc(ends, func(a, b uint64) int { return cmp.Compare(b, a) })
		var Li int
		for _, k := range ends {
			v := hph.hadToLoadL[k]
			accs := fmt.Sprintf("load=%s skip=%s (%.1f%%) reset %.1f%%", common.PrettyCounter(v.accLoaded), common.PrettyCounter(v.accSkipped), 100*(float64(v.accSkipped)/float64(v.accLoaded+v.accSkipped)), 100*(float64(v.accReset)/float64(v.accReset+v.accSkipped)))
			stors := fmt.Sprintf("load=%s skip=%s (%.1f%%) reset %.1f%%", common.PrettyCounter(v.storLoaded), common.PrettyCounter(v.storSkipped), 100*(float64(v.storSkipped)/float64(v.storLoaded+v.storSkipped)), 100*(float64(v.storReset)/float64(v.storReset+v.storSkipped)))
			if k == 0 {
				log.Debug("branchData memoization, new branches", "endStep", k, "accounts", accs, "storages", stors)
			} else {
				log.Debug("branchData memoization", "L", Li, "endStep", k, "accounts", accs, "storages", stors)
				Li++

				mxTrieStateLevelledSkipRatesAccount[min(Li, 5)].Add(float64(v.accSkipped))
				mxTrieStateLevelledSkipRatesStorage[min(Li, 5)].Add(float64(v.storSkipped))
				mxTrieStateLevelledLoadRatesAccount[min(Li, 5)].Add(float64(v.accLoaded))
				mxTrieStateLevelledLoadRatesStorage[min(Li, 5)].Add(float64(v.storLoaded))
			}
		}
	}

	return rootHash, nil
}

func (hph *HexPatriciaHashed) SetTraceWriter(w io.Writer) { hph.traceW = w }

func (hph *HexPatriciaHashed) EnableCsvMetrics(filePathPrefix string) {
	hph.metrics.EnableCsvMetrics(filePathPrefix)
	hph.cfg.CsvMetricsFilePrefix = filePathPrefix
}

func (hph *HexPatriciaHashed) Variant() TrieVariant { return VariantHexPatriciaTrie }

// TakeDeferredUpdates returns the current deferred updates from the branch encoder
// and gives it a fresh empty slice. Caller takes ownership of the returned slice.
func (hph *HexPatriciaHashed) TakeDeferredUpdates() []*DeferredBranchUpdate {
	deferred := hph.branchEncoder.deferred
	hph.branchEncoder.deferred = make([]*DeferredBranchUpdate, 0, 64)
	if hph.branchEncoder.pendingPrefixes != nil {
		hph.branchEncoder.pendingPrefixes.Clear()
	}
	ResetDeferredUpdateMetrics()
	return deferred
}

// HasPendingDeferredUpdates returns true if the branch encoder has non-empty deferred updates.
func (hph *HexPatriciaHashed) HasPendingDeferredUpdates() bool {
	return len(hph.branchEncoder.deferred) > 0
}

// ApplyAndClearInlineDeferredUpdates applies deferred updates inline via ctx.PutBranch and clears them.
func (hph *HexPatriciaHashed) ApplyAndClearInlineDeferredUpdates() error {
	if err := hph.branchEncoder.ApplyDeferredUpdates(runtime.NumCPU(), hph.ctx.PutBranch); err != nil {
		return fmt.Errorf("apply deferred updates: %w", err)
	}
	hph.branchEncoder.ClearDeferred()
	return nil
}

// SetLeaveDeferredForCaller controls whether Process() leaves deferred updates on the
// branchEncoder for the caller to handle (true) or applies them inline (false, default).
func (hph *HexPatriciaHashed) SetLeaveDeferredForCaller(leave bool) {
	hph.leaveDeferredForCaller = leave
}

// Reset allows HexPatriciaHashed instance to be reused for the new commitment calculation.
// The aggregator-scope BranchCache is intentionally not cleared here;
// SharedDomains.Unwind handles correctness via txN-tagged eviction.
func (hph *HexPatriciaHashed) Reset() {
	hph.root.reset()
	hph.rootTouched = false
	hph.rootChecked = false
	hph.rootPresent = true
}

func (hph *HexPatriciaHashed) ResetContext(ctx PatriciaContext) {
	hph.ctx = ctx
}

// Reads via ctx.Branch (sd.mem → parent.mem → BranchCache → MDBX).
func (hph *HexPatriciaHashed) branchFromCacheOrDB(key []byte) ([]byte, error) {
	data, _, err := hph.ctx.Branch(key)
	return data, err
}

// No Go-side cache; reads straight from the AccountsDomain.
func (hph *HexPatriciaHashed) accountFromCacheOrDB(plainKey []byte) (*Update, error) {
	return hph.ctx.Account(plainKey)
}

// No Go-side cache; reads straight from the StorageDomain.
func (hph *HexPatriciaHashed) storageFromCacheOrDB(plainKey []byte) (*Update, error) {
	return hph.ctx.Storage(plainKey)
}

type stateRootFlag int8

var (
	stateRootPresent stateRootFlag = 1
	stateRootChecked stateRootFlag = 2
	stateRootTouched stateRootFlag = 4
)

// represents state of the tree
type state struct {
	Root         []byte      // encoded root cell
	Depths       [128]int16  // For each row, the depth of cells in that row
	TouchMap     [128]uint16 // For each row, bitmap of cells that were either present before modification, or modified or deleted
	AfterMap     [128]uint16 // For each row, bitmap of cells that were present after modification
	BranchBefore [128]bool   // For each row, whether there was a branch node in the database loaded in unfold
	RootChecked  bool        // Set to false if it is not known whether the root is empty, set to true if it is checked
	RootTouched  bool
	RootPresent  bool
}

func (s *state) Encode(buf []byte) ([]byte, error) {
	var rootFlags stateRootFlag
	if s.RootPresent {
		rootFlags |= stateRootPresent
	}
	if s.RootChecked {
		rootFlags |= stateRootChecked
	}
	if s.RootTouched {
		rootFlags |= stateRootTouched
	}

	ee := bytes.NewBuffer(buf)
	if err := binary.Write(ee, binary.BigEndian, int8(rootFlags)); err != nil {
		return nil, fmt.Errorf("encode rootFlags: %w", err)
	}
	if err := binary.Write(ee, binary.BigEndian, uint16(len(s.Root))); err != nil {
		return nil, fmt.Errorf("encode root len: %w", err)
	}
	if n, err := ee.Write(s.Root); err != nil || n != len(s.Root) {
		return nil, fmt.Errorf("encode root: %w", err)
	}
	d := make([]byte, len(s.Depths))
	for i := range len(s.Depths) {
		d[i] = byte(s.Depths[i])
	}
	if n, err := ee.Write(d); err != nil || n != len(s.Depths) {
		return nil, fmt.Errorf("encode depths: %w", err)
	}
	if err := binary.Write(ee, binary.BigEndian, s.TouchMap); err != nil {
		return nil, fmt.Errorf("encode touchMap: %w", err)
	}
	if err := binary.Write(ee, binary.BigEndian, s.AfterMap); err != nil {
		return nil, fmt.Errorf("encode afterMap: %w", err)
	}

	var before1, before2 uint64
	for i := range 64 {
		if s.BranchBefore[i] {
			before1 |= 1 << i
		}
	}
	for i, j := 64, 0; i < 128; i, j = i+1, j+1 {
		if s.BranchBefore[i] {
			before2 |= 1 << j
		}
	}
	if err := binary.Write(ee, binary.BigEndian, before1); err != nil {
		return nil, fmt.Errorf("encode branchBefore_1: %w", err)
	}
	if err := binary.Write(ee, binary.BigEndian, before2); err != nil {
		return nil, fmt.Errorf("encode branchBefore_2: %w", err)
	}
	return ee.Bytes(), nil
}

func (s *state) Decode(buf []byte) error {
	aux := bytes.NewBuffer(buf)
	var rootFlags stateRootFlag
	if err := binary.Read(aux, binary.BigEndian, &rootFlags); err != nil {
		return fmt.Errorf("rootFlags: %w", err)
	}

	if rootFlags&stateRootPresent != 0 {
		s.RootPresent = true
	}
	if rootFlags&stateRootTouched != 0 {
		s.RootTouched = true
	}
	if rootFlags&stateRootChecked != 0 {
		s.RootChecked = true
	}

	var rootSize uint16
	if err := binary.Read(aux, binary.BigEndian, &rootSize); err != nil {
		return fmt.Errorf("root size: %w", err)
	}
	s.Root = make([]byte, rootSize)
	if _, err := aux.Read(s.Root); err != nil {
		return fmt.Errorf("root: %w", err)
	}
	d := make([]byte, len(s.Depths))
	if err := binary.Read(aux, binary.BigEndian, &d); err != nil {
		return fmt.Errorf("depths: %w", err)
	}
	for i := range len(s.Depths) {
		s.Depths[i] = int16(d[i])
	}
	if err := binary.Read(aux, binary.BigEndian, &s.TouchMap); err != nil {
		return fmt.Errorf("touchMap: %w", err)
	}
	if err := binary.Read(aux, binary.BigEndian, &s.AfterMap); err != nil {
		return fmt.Errorf("afterMap: %w", err)
	}
	var branch1, branch2 uint64
	if err := binary.Read(aux, binary.BigEndian, &branch1); err != nil {
		return fmt.Errorf("branchBefore1: %w", err)
	}
	if err := binary.Read(aux, binary.BigEndian, &branch2); err != nil {
		return fmt.Errorf("branchBefore2: %w", err)
	}

	for i := range 64 {
		if branch1&(1<<i) != 0 {
			s.BranchBefore[i] = true
		}
	}
	for i, j := 64, 0; i < 128; i, j = i+1, j+1 {
		if branch2&(1<<j) != 0 {
			s.BranchBefore[i] = true
		}
	}
	return nil
}

func (cell *cell) Encode() []byte {
	var pos = int16(1)
	size := pos + 5 + cell.hashLen + cell.accountAddrLen + cell.storageAddrLen + cell.hashedExtLen + cell.extLen // max size
	buf := make([]byte, size)

	var flags uint8
	if cell.hashLen != 0 {
		flags |= cellFlagHash
		buf[pos] = byte(cell.hashLen)
		pos++
		copy(buf[pos:pos+cell.hashLen], cell.hash[:])
		pos += cell.hashLen
	}
	if cell.accountAddrLen != 0 {
		flags |= cellFlagAccount
		buf[pos] = byte(cell.accountAddrLen)
		pos++
		copy(buf[pos:pos+cell.accountAddrLen], cell.accountAddr[:])
		pos += cell.accountAddrLen
	}
	if cell.storageAddrLen != 0 {
		flags |= cellFlagStorage
		buf[pos] = byte(cell.storageAddrLen)
		pos++
		copy(buf[pos:pos+cell.storageAddrLen], cell.storageAddr[:])
		pos += cell.storageAddrLen
	}
	if cell.hashedExtLen != 0 {
		flags |= cellFlagDownHash
		buf[pos] = byte(cell.hashedExtLen)
		pos++
		copy(buf[pos:pos+cell.hashedExtLen], cell.hashedExtension[:cell.hashedExtLen])
		pos += cell.hashedExtLen
	}
	if cell.extLen != 0 {
		flags |= cellFlagExtension
		buf[pos] = byte(cell.extLen)
		pos++
		copy(buf[pos:pos+cell.extLen], cell.extension[:])
		pos += cell.extLen //nolint:ineffassign
	}
	if cell.Deleted() {
		flags |= cellFlagDelete
	}
	buf[0] = flags
	return buf
}

const (
	cellFlagHash = uint8(1 << iota)
	cellFlagAccount
	cellFlagStorage
	cellFlagDownHash
	cellFlagExtension
	cellFlagDelete
)

func (cell *cell) Decode(buf []byte) error {
	if len(buf) < 1 {
		return errors.New("invalid buffer size to contain cell (at least 1 byte expected)")
	}
	cell.reset()

	var pos int16
	flags := buf[pos]
	pos++

	if flags&cellFlagHash != 0 {
		cell.hashLen = int16(buf[pos])
		pos++
		copy(cell.hash[:], buf[pos:pos+cell.hashLen])
		pos += cell.hashLen
	}
	if flags&cellFlagAccount != 0 {
		cell.accountAddrLen = int16(buf[pos])
		pos++
		copy(cell.accountAddr[:], buf[pos:pos+cell.accountAddrLen])
		pos += cell.accountAddrLen
	}
	if flags&cellFlagStorage != 0 {
		cell.storageAddrLen = int16(buf[pos])
		pos++
		copy(cell.storageAddr[:], buf[pos:pos+cell.storageAddrLen])
		pos += cell.storageAddrLen
	}
	if flags&cellFlagDownHash != 0 {
		cell.hashedExtLen = int16(buf[pos])
		pos++
		copy(cell.hashedExtension[:], buf[pos:pos+cell.hashedExtLen])
		pos += cell.hashedExtLen
	}
	if flags&cellFlagExtension != 0 {
		cell.extLen = int16(buf[pos])
		pos++
		copy(cell.extension[:], buf[pos:pos+cell.extLen])
		pos += cell.extLen //nolint:ineffassign
	}
	if flags&cellFlagDelete != 0 {
		log.Warn("deleted cell should not be encoded", "cell", cell.String())
		cell.Update.Flags = DeleteUpdate
	}
	return nil
}

// Encode current state of hph into bytes
func (hph *HexPatriciaHashed) EncodeCurrentState(buf []byte) ([]byte, error) {
	s := state{
		RootChecked: hph.rootChecked,
		RootTouched: hph.rootTouched,
		RootPresent: hph.rootPresent,
	}
	if hph.currentKeyLen > 0 {
		panic("currentKeyLen > 0")
	}

	s.Root = hph.root.Encode()
	copy(s.Depths[:], hph.depths[:])
	copy(s.BranchBefore[:], hph.branchBefore[:])
	copy(s.TouchMap[:], hph.touchMap[:])
	copy(s.AfterMap[:], hph.afterMap[:])

	return s.Encode(buf)
}

// buf expected to be encoded hph state. Decode state and set up hph to that state.
func (hph *HexPatriciaHashed) SetState(buf []byte) error {
	hph.Reset()

	if buf == nil {
		// reset state to 'empty'
		hph.currentKeyLen = 0
		hph.rootChecked = false
		hph.rootTouched = false
		hph.rootPresent = false
		hph.activeRows = 0

		for i := range len(hph.depths) {
			hph.depths[i] = 0
			hph.branchBefore[i] = false
			hph.touchMap[i] = 0
			hph.afterMap[i] = 0
		}
		return nil
	}
	if hph.activeRows != 0 {
		return errors.New("target trie has active rows, could not reset state before fold")
	}

	var s state
	if err := s.Decode(buf); err != nil {
		return err
	}

	if err := hph.root.Decode(s.Root); err != nil {
		return err
	}
	hph.rootChecked = s.RootChecked
	hph.rootTouched = s.RootTouched
	hph.rootPresent = s.RootPresent

	copy(hph.depths[:], s.Depths[:])
	copy(hph.branchBefore[:], s.BranchBefore[:])
	copy(hph.touchMap[:], s.TouchMap[:])
	copy(hph.afterMap[:], s.AfterMap[:])

	if hph.root.accountAddrLen > 0 {
		if hph.ctx == nil {
			panic("nil ctx")
		}

		update, err := hph.accountFromCacheOrDB(hph.root.accountAddr[:hph.root.accountAddrLen])
		if err != nil {
			return err
		}
		hph.root.setFromUpdate(update)
	}
	if hph.root.storageAddrLen > 0 {
		if hph.ctx == nil {
			panic("nil ctx")
		}
		update, err := hph.storageFromCacheOrDB(hph.root.storageAddr[:hph.root.storageAddrLen])
		if err != nil {
			return err
		}
		hph.root.setFromUpdate(update)
	}
	// A leaf root's navigation path is derivable but not reliably persisted: without it a
	// wall probe sees an unfoldable root and the mount paths overwrite the leaf in place.
	if hph.root.accountAddrLen > 0 || hph.root.storageAddrLen > 0 {
		hph.root.hashedExtLen = 0
		if err := hph.root.deriveHashedKeys(0, hph.keccak, hph.accountKeyLen, hph.cellHashBuf[:]); err != nil {
			return err
		}
	}
	// Blobs written before propagate folds set rootPresent carry false for non-empty
	// roots; a non-empty root is present by definition.
	if !hph.root.IsEmpty() {
		hph.rootPresent = true
	}

	return nil
}

func HexTrieExtractStateRoot(enc []byte) ([]byte, uint64, uint64, error) {
	if len(enc) < 18 { // 8*2+2
		return nil, 0, 0, fmt.Errorf("invalid state length %x (min %d expected)", len(enc), 18)
	}

	txn := binary.BigEndian.Uint64(enc)
	bn := binary.BigEndian.Uint64(enc[8:])
	sl := binary.BigEndian.Uint16(enc[16:18])
	var s state
	if err := s.Decode(enc[18 : 18+sl]); err != nil {
		return nil, 0, 0, err
	}
	root := new(cell)
	if err := root.Decode(s.Root); err != nil {
		return nil, 0, 0, err
	}
	return root.hash[:], bn, txn, nil
}

func HexTrieStateToShortString(enc []byte) (string, error) {
	if len(enc) < 18 {
		return "", fmt.Errorf("invalid state length %x (min %d expected)", len(enc), 18)
	}
	txn := binary.BigEndian.Uint64(enc)
	bn := binary.BigEndian.Uint64(enc[8:])
	sl := binary.BigEndian.Uint16(enc[16:18])

	var s state
	if err := s.Decode(enc[18 : 18+sl]); err != nil {
		return "", err
	}
	root := new(cell)
	if err := root.Decode(s.Root); err != nil {
		return "", err
	}
	return fmt.Sprintf("block: %d txn: %d rootHash: %x", bn, txn, root.hash[:]), nil
}

func HexTrieStateToString(enc []byte) (string, error) {
	if len(enc) < 18 {
		return "", fmt.Errorf("invalid state length %x (min %d expected)", len(enc), 18)
	}
	txn := binary.BigEndian.Uint64(enc)
	bn := binary.BigEndian.Uint64(enc[8:])
	sl := binary.BigEndian.Uint16(enc[16:18])

	var s state
	sb := new(strings.Builder)
	if err := s.Decode(enc[18 : 18+sl]); err != nil {
		return "", err
	}
	fmt.Fprintf(sb, "block: %d txn: %d\n", bn, txn)
	// fmt.Fprintf(sb, " touchMaps: %v\n", s.TouchMap)
	// fmt.Fprintf(sb, " afterMaps: %v\n", s.AfterMap)
	// fmt.Fprintf(sb, " depths: %v\n", s.Depths)

	printAfterMap := func(sb *strings.Builder, name string, list []uint16, depths []int16, existedBefore []bool) {
		fmt.Fprintf(sb, "\t::%s::\n\n", name)
		lastNonZero := 0
		for i, l := range slices.Backward(list) {
			if l != 0 {
				lastNonZero = i
				break
			}
		}
		for i, v := range list {
			newBranchSuf := ""
			if !existedBefore[i] {
				newBranchSuf = " NEW"
			}

			fmt.Fprintf(sb, " d=%3d %016b%s\n", depths[i], v, newBranchSuf)
			if i == lastNonZero {
				break
			}
		}
	}
	fmt.Fprintf(sb, " rootNode: %x [touched=%t, present=%t, checked=%t]\n", s.Root, s.RootTouched, s.RootPresent, s.RootChecked)

	root := new(cell)
	if err := root.Decode(s.Root); err != nil {
		return "", err
	}

	fmt.Fprintf(sb, "RootHash: %x\n", root.hash)
	printAfterMap(sb, "afterMap", s.AfterMap[:], s.Depths[:], s.BranchBefore[:])

	return sb.String(), nil
}

func (hph *HexPatriciaHashed) Grid() [128][16]cell {
	return hph.grid
}
