// 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"
	"context"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"math/bits"
	"runtime"
	"sort"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	keccak "github.com/erigontech/fastkeccak"

	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/common/crypto"
	"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"
	witnesstypes "github.com/erigontech/erigon/execution/commitment/witness"
	"github.com/erigontech/erigon/execution/rlp"
	"github.com/erigontech/erigon/execution/types/accounts"
)

// 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

// 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
	trace         bool
	traceDomain   bool
	capture       []string
	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)
	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
	mountedTries []*HexPatriciaHashed // list of mounted tries to unmount

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

	// Warmup cache for serving reads from pre-warmed data
	cache             *WarmupCache
	enableWarmupCache bool // if true, enables warmup cache during Process (false by default)

	// 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

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

// Clones current trie state to allow concurrent processing.
func (hph *HexPatriciaHashed) SpawnSubTrie(ctx PatriciaContext, forNibble int) *HexPatriciaHashed {
	subTrie := NewHexPatriciaHashed(hph.accountKeyLen, ctx)
	// Disable deferred updates for sub-tries since they fold directly
	// and their deferred updates would never be applied
	subTrie.branchEncoder.SetDeferUpdates(false)

	subTrie.mountTo(hph, forNibble)
	return subTrie
}

var hphPool sync.Pool

func NewHexPatriciaHashed(accountKeyLen int16, ctx PatriciaContext) *HexPatriciaHashed {
	hph, ok := hphPool.Get().(*HexPatriciaHashed)
	if !ok {
		hph = newHexPatriciaHashed()
	}
	hph.resetForReuse()
	hph.accountKeyLen = accountKeyLen
	hph.ctx = ctx
	return hph
}

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)
	hph.branchEncoder.SetDeferUpdates(true) // Enable deferred branch updates by default
	return hph
}

// 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)

	// reuse slice backing
	hph.mountedTries = hph.mountedTries[:0]
	hph.mounted = false
	hph.mountedNib = 0

	// warmup cache
	hph.cache = nil
	hph.enableWarmupCache = false

	// tracing / capture
	hph.capture = nil
	hph.trace = false
	hph.traceDomain = false

	// flags
	hph.memoizationOff = false
	hph.leaveDeferredForCaller = false

	// auxiliary buffer
	hph.auxBuffer.Reset()

	// branch encoder: clear deferred updates, reset buffer, nil cache, re-enable deferred
	hph.branchEncoder.ClearDeferred()
	hph.branchEncoder.buf.Reset()
	hph.branchEncoder.cache = nil
	hph.branchEncoder.SetDeferUpdates(true)

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

// Release returns this HexPatriciaHashed to the pool for reuse.
// After calling Release, the caller must not use the struct.
func (hph *HexPatriciaHashed) Release() {
	hph.ctx = nil
	hph.cache = nil
	hph.mountedTries = nil
	hph.capture = nil
	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)
)

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.Bytes()
)

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)
		mxTrieStateLoadRate.Inc()
		hadToLoad.Add(1)
	}
	if update.Flags&BalanceUpdate != 0 || update.Flags&NonceUpdate != 0 || update.Flags&CodeUpdate != 0 {
		cell.loaded = cell.loaded.addFlag(cellLoadAccount)
		mxTrieStateLoadRate.Inc()
		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))
		} 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])
	}
	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
}

// extractBranchCellAddresses extracts account/storage addresses from branch data.
// pathNibble is the nibble on the update path (-1 if none) - its addresses are always
// extracted since that cell will have stateHash cleared during update.
// Sibling cells with stateHash (memoized) are skipped.
func extractBranchCellAddresses(branchData []byte, pathNibble int) (accountAddrs [][]byte, storageAddrs [][]byte) {
	if len(branchData) < 4 {
		return nil, nil
	}
	// Skip touch map (first 2 bytes)
	branchData = branchData[2:]
	bitmap := binary.BigEndian.Uint16(branchData[0:2])
	pos := 2

	// Iterate over all cells in the branch
	for bitset := bitmap; bitset != 0; {
		bit := bitset & -bitset
		nibble := bits.TrailingZeros16(bit)
		bitset ^= bit

		if pos >= len(branchData) {
			break
		}
		fieldBits := branchData[pos]
		pos++

		// Cell on update path will have stateHash cleared - always extract its addresses.
		// Sibling cells with stateHash (bit 4) are memoized - skip them.
		isOnPath := nibble == pathNibble
		hasMemoizedHash := fieldBits&16 != 0
		shouldExtract := isOnPath || !hasMemoizedHash

		// Parse each field
		// extension (bit 0)
		if fieldBits&1 != 0 {
			if pos >= len(branchData) {
				break
			}
			l, n := binary.Uvarint(branchData[pos:])
			if n <= 0 {
				break
			}
			pos += n + int(l)
		}

		// accountAddr (bit 1)
		if fieldBits&2 != 0 {
			if pos >= len(branchData) {
				break
			}
			l, n := binary.Uvarint(branchData[pos:])
			if n <= 0 {
				break
			}
			pos += n
			if l > 0 && pos+int(l) <= len(branchData) {
				if shouldExtract {
					addr := make([]byte, l)
					copy(addr, branchData[pos:pos+int(l)])
					accountAddrs = append(accountAddrs, addr)
				}
				pos += int(l)
			}
		}

		// storageAddr (bit 2)
		if fieldBits&4 != 0 {
			if pos >= len(branchData) {
				break
			}
			l, n := binary.Uvarint(branchData[pos:])
			if n <= 0 {
				break
			}
			pos += n
			if l > 0 && pos+int(l) <= len(branchData) {
				if shouldExtract {
					addr := make([]byte, l)
					copy(addr, branchData[pos:pos+int(l)])
					storageAddrs = append(storageAddrs, addr)
				}
				pos += int(l)
			}
		}

		// hash (bit 3)
		if fieldBits&8 != 0 {
			if pos >= len(branchData) {
				break
			}
			l, n := binary.Uvarint(branchData[pos:])
			if n <= 0 {
				break
			}
			pos += n + int(l)
		}

		// stateHash (bit 4)
		if fieldBits&16 != 0 {
			if pos >= len(branchData) {
				break
			}
			l, n := binary.Uvarint(branchData[pos:])
			if n <= 0 {
				break
			}
			pos += n + int(l)
		}
	}
	return accountAddrs, storageAddrs
}

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
	var keyPrefix [1]byte
	if compactLen > 1 {
		keyPrefix[0] = 0x80 + byte(compactLen)
		kp = 1
		kl = compactLen
	} else {
		kl = 1
	}

	totalLen := kp + kl + val.DoubleRLPLen()
	var lenPrefix [4]byte
	pl := rlp.GenerateStructLen(lenPrefix[:], totalLen)
	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.keccak
	}
	if _, err := writer.Write(lenPrefix[:pl]); err != nil {
		return nil, err
	}
	if _, err := writer.Write(keyPrefix[:kp]); err != nil {
		return nil, err
	}
	b := [1]byte{compact0}
	if _, err := writer.Write(b[:]); err != nil {
		return nil, err
	}
	for i := 1; i < compactLen; i++ {
		b[0] = key[ni]*16 + key[ni+1]
		if _, err := writer.Write(b[:]); err != nil {
			return nil, err
		}
		ni += 2
	}
	var prefixBuf [8]byte
	if err := val.ToDoubleRLP(writer, prefixBuf[:]); err != nil {
		return nil, err
	}
	if canEmbed {
		buf = hph.auxBuffer.Bytes()
	} else {
		var hashBuf [33]byte
		hashBuf[0] = 0x80 + length.Hash
		if _, err := hph.keccak.Read(hashBuf[1:]); err != nil {
			return nil, err
		}
		buf = append(buf, hashBuf[:]...)
	}
	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.GenerateStructLen(lenPrefix[:], totalLen)
	hph.keccak.Reset()
	if _, err := hph.keccak.Write(lenPrefix[:pt]); err != nil {
		return hashBuf, err
	}
	if _, err := hph.keccak.Write(keyPrefix[:kp]); err != nil {
		return hashBuf, err
	}
	var b [1]byte
	b[0] = compact0
	if _, err := hph.keccak.Write(b[:]); err != nil {
		return hashBuf, err
	}
	for i := 1; i < compactLen; i++ {
		b[0] = key[ni]*16 + key[ni+1]
		if _, err := hph.keccak.Write(b[:]); err != nil {
			return hashBuf, err
		}
		ni += 2
	}
	b[0] = 0x80 + length.Hash
	if _, err := hph.keccak.Write(b[:]); err != nil {
		return hashBuf, err
	}
	if _, err := hph.keccak.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
	}
	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.GenerateStructLen(lenPrefix[:], totalLen)
		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.trace {
				fmt.Printf("REUSED stateHash %x spk %x\n", res, cell.storageAddr[:cell.storageAddrLen])
			}
			mxTrieStateSkipRate.Inc()
			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.trace {
					fmt.Printf("Storage %x was not loaded\n", cell.storageAddr[:cell.storageAddrLen])
				}
			}
			if singleton {
				if hph.trace {
					fmt.Printf("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.trace {
					fmt.Printf("leafHashWithKeyVal(singleton) storage hash [%x]\n", aux)
				}
				storageRootHash = *(*common.Hash)(aux[1:])
				storageRootHashIsSet = true
				cell.stateHashLen = 0
				hadToReset.Add(1)
			} else {
				if hph.trace {
					fmt.Printf("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.trace {
					fmt.Printf("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.trace {
					fmt.Printf("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.trace {
					fmt.Printf("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()

				mxTrieStateSkipRate.Inc()
				skippedLoad.Add(1)
				if hph.trace {
					fmt.Printf("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.trace {
			fmt.Printf("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.trace {
			fmt.Printf("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.trace {
				fmt.Printf("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.trace {
				fmt.Printf("REUSED stateHash %x spk %x\n", cell.stateHash[:cell.stateHashLen], cell.storageAddr[:cell.storageAddrLen])
			}
			mxTrieStateSkipRate.Inc()
			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.trace {
				fmt.Printf("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.trace {
					fmt.Printf("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.trace {
					fmt.Printf("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()

				mxTrieStateSkipRate.Inc()
				skippedLoad.Add(1)
				if hph.trace {
					fmt.Printf("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.trace {
			fmt.Printf("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.trace {
				fmt.Printf("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
}

func (hph *HexPatriciaHashed) needUnfolding(hashedKey []byte) int16 {
	var cell *cell
	var depth int16
	if hph.activeRows == 0 {
		if hph.trace {
			fmt.Printf("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.trace {
				fmt.Printf("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 {
		nibble := int(hashedKey[hph.currentKeyLen])
		cell = &hph.grid[hph.activeRows-1][nibble]
		depth = hph.depths[hph.activeRows-1]
		if hph.trace {
			fmt.Printf("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.trace {
		fmt.Printf("cpl=%d cell.hashedExtension=[%x] hashedKey[depth=%d:]=[%x]\n", cpl, cell.hashedExtension[:cell.hashedExtLen], depth, hashedKey[depth:])
	}
	unfolding := int16(cpl + 1)
	if depth < 64 && depth+unfolding > 64 {
		// This is to make sure that unfolding always breaks at the level where storage subtrees start
		unfolding = 64 - depth
		if hph.trace {
			fmt.Printf("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 := 0; col < 16; col++ {
			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")
}

// this function is only related to the witness
func (hph *HexPatriciaHashed) witnessCreateAccountNode(c *cell, depth int16, hashedKey []byte, codeReads map[common.Hash]witnesstypes.CodeWithHash) (*trie.AccountNode, error) {
	_, storageIsSet, storageRootHash, err := hph.witnessComputeCellHashWithStorage(c, depth, nil)
	if err != nil {
		return nil, err
	}
	accountUpdate, err := hph.accountFromCacheOrDB(c.accountAddr[:c.accountAddrLen])
	if err != nil {
		return nil, err
	}
	var account accounts.Account
	account.Nonce = accountUpdate.Nonce
	account.Balance = accountUpdate.Balance
	account.Root = accountUpdate.Storage
	account.CodeHash = accounts.InternCodeHash(accountUpdate.CodeHash)

	addrHash, err := CompactKey(hashedKey[:64])
	if err != nil {
		return nil, err
	}

	// get code
	var code []byte
	codeWithHash, hasCode := codeReads[[32]byte(addrHash)]
	if !hasCode {
		code = nil
	} else {
		code = codeWithHash.Code
		// sanity check
		if account.CodeHash != codeWithHash.CodeHash {
			return nil, fmt.Errorf("account.CodeHash(%x)!=codeReads[%x].CodeHash(%x)", account.CodeHash, addrHash, codeWithHash.CodeHash)
		}
	}

	var accountNode *trie.AccountNode
	if !storageIsSet {
		account.Root = trie.EmptyRoot
		accountNode = &trie.AccountNode{Account: account, Storage: nil, RootCorrect: true, Code: code, CodeSize: -1}
	} else {
		accountNode = &trie.AccountNode{Account: account, Storage: trie.NewHashNode(storageRootHash), RootCorrect: true, Code: code, CodeSize: -1}
	}
	return accountNode, nil
}

// func readBranchData(hph *HexPatriciaHashed, key []byte) ([16]*cell, error) {
// 	var rowData [16]*cell
// 	compactKey := hexNibblesToCompactBytes(key)
// 	branchData, _, err := hph.ctx.Branch(compactKey)
// 	if err != nil {
// 		return rowData, err
// 	}
// 	if branchData == nil {
// 		return rowData, fmt.Errorf("empty branch data for key %x", compactKey)
// 	}
// 	bd := BranchData(branchData)
// 	_, _, rowData, err = bd.decodeCells()
// 	if err != nil {
// 		return rowData, err
// 	}
// 	return rowData, nil
// }

// // number of non-empty cells in a row
// func nCells(rowData [16]*cell) int {
// 	var count int = 0
// 	for i := 0; i < 16; i++ {
// 		if !rowData[i].IsEmpty() {
// 			count++
// 		}
// 	}
// 	return count
// }

// // first index in row where the cell is not empty
// func firstNonEmptyIdx(rowData [16]*cell) int {
// 	for i := 0; i < 16; i++ {
// 		if !rowData[i].IsEmpty() {
// 			return i
// 		}
// 	}
// 	return -1
// }

// func terminalRowToFullNode(hph *HexPatriciaHashed, rowData [16]*cell, depth int16) (*trie.FullNode, error) {
// 	var fullNode trie.FullNode
// 	for i := 0; i < 16; i++ {
// 		c := rowData[i]
// 		if c.IsEmpty() {
// 			fullNode.Children[i] = nil
// 		} else if c.hashLen > 0 { // hash nod
// 			fullNode.Children[i] = trie.NewHashNode(c.hash[:c.hashLen])
// 		} else if c.accountAddrLen > 0 { // account node
// 			addrHash := KeyToHexNibbleHash(c.accountAddr[:c.accountAddrLen])
// 			accNode, err := hph.witnessCreateAccountNode(c, depth, addrHash, nil)
// 			if err != nil {
// 				return nil, err
// 			}
// 			fullNode.Children[i] = accNode
// 		} else if c.storageAddrLen > 0 { // storage node
// 			storageUpdate, err := hph.storageFromCacheOrDB(c.storageAddr[:c.storageAddrLen])
// 			if err != nil {
// 				return nil, err
// 			}
// 			storageValueNode := trie.ValueNode(storageUpdate.Storage[:storageUpdate.StorageLen])
// 			fullNode.Children[i] = storageValueNode
// 		} else if c.hashedExtLen > 0 { // extension node, but we don't have the hash, we would need to traverse further. Throw error for now
// 			return nil, fmt.Errorf("unexpected cell with hashedExtKey=%x", c.hashedExtension[:c.hashedExtLen])
// 		}
// 	}
// 	return &fullNode, nil
// }

// func terminalRowToNode(hph *HexPatriciaHashed, rowData [16]*cell, depth int16) (trie.Node, error) {
// 	var terminalNode trie.Node
// 	var err error
// 	nrCells := nCells(rowData)
// 	if nrCells > 1 { // branch node
// 		terminalNode, err = terminalRowToFullNode(hph, rowData, depth)
// 		if err != nil {
// 			return nil, err
// 		}
// 	} else if nrCells == 1 {
// 		idx := firstNonEmptyIdx(rowData)
// 		c := rowData[idx]
// 		if c.hashLen > 0 { // HashNode
// 			terminalNode = trie.NewHashNode(c.hash[:c.hashLen])
// 		} else if c.accountAddrLen > 0 { // AccountNode
// 			addrHash := KeyToHexNibbleHash(c.accountAddr[:c.accountAddrLen])
// 			terminalNode, err = hph.witnessCreateAccountNode(c, depth, addrHash, nil)
// 			if err != nil {
// 				return nil, err
// 			}
// 		} else if c.storageAddrLen > 0 { // Storage Value
// 			storageUpdate, err := hph.storageFromCacheOrDB(c.storageAddr[:c.storageAddrLen])
// 			if err != nil {
// 				return nil, err
// 			}
// 			terminalNode = trie.ValueNode(storageUpdate.Storage[:storageUpdate.StorageLen])
// 		} else {
// 			return nil, fmt.Errorf("unexpected type of terminal cell %s", c)
// 		}
// 	}
// 	return terminalNode, nil
// }

// Traverse the grid following `hashedKey` and produce the witness `triedeprecated.Trie` for that key
func (hph *HexPatriciaHashed) toWitnessTrie(hashedKey []byte, codeReads map[common.Hash]witnesstypes.CodeWithHash) (*trie.Trie, error) {
	var rootNode trie.Node = &trie.FullNode{}
	var currentNode trie.Node = rootNode
	keyPos := int16(0) // current position in hashedKey (usually same as row, but could be different due to extension nodes)

	if hph.root.hashedExtLen > 0 {
		extKey := common.Copy(hph.root.hashedExtension[:hph.root.hashedExtLen])
		if len(extKey) == 64 {
			extKey = append(extKey, terminatorHexByte) // append terminator byte
		}
		currentNode = &trie.ShortNode{Key: extKey, Val: &trie.FullNode{}}
		// currentNode = &trie.ShortNode{Val: &trie.FullNode{}}
		rootNode = currentNode             // use root node as the current node
		keyPos = hph.root.hashedExtLen - 1 // start from the end of the root extension
		fmt.Printf("[witness] root node %s, pos %d\n", hph.root.FullString(), keyPos)
	}

	pathDivergenceFound := false // indicates if the extension node has a common prefix path that diverges from what is found in the hashedKey
	for row := 0; row < hph.activeRows && keyPos < int16(len(hashedKey)); row++ {
		if pathDivergenceFound { // path divergence found in previous iteration, cannot expand further the proof trie
			break
		}
		currentNibble := hashedKey[keyPos]
		// determine the type of the next node to expand (in the next iteration)
		var nextNode trie.Node
		// need to check node type along the key path
		cellToExpand := &hph.grid[row][currentNibble]
		// determine the next node
		if cellToExpand.hashedExtLen > 0 { // extension cell
			depthAdjusted := false
			extKeyLength := cellToExpand.hashedExtLen
			if hph.depths[row] < 64 && extKeyLength+hph.depths[row] > 64 { //&& cellToExpand.accountAddrLen > 0 {
				extKeyLength = 64 - hph.depths[row] // adjust depth to stop before storage trie
				depthAdjusted = true
				if hph.trace {
					fmt.Printf("[witness] adjusted hashExtLen=%d <- %d\n", extKeyLength, cellToExpand.hashedExtLen)
				}
			}
			hashedExtKey := cellToExpand.hashedExtension[:extKeyLength]
			// the corresponding path in the hashed key
			hashedKeySubstring := hashedKey[keyPos+1 : keyPos+extKeyLength+1]
			fullPathLength := int(keyPos+1) + len(hashedExtKey)
			// the diverging extension node points to a branch node in this case
			if !bytes.Equal(hashedExtKey, hashedKeySubstring) && fullPathLength != 64 && fullPathLength != 128 {
				// path has diverged due to the hashedKey not leading to any account or storage
				// the traversal can be stopped at this level
				pathDivergenceFound = true
				// special handling only if consuming the diverging hashed extension doesn't lead to account or storage
				// Code left commented out in case it might be needed in the future:
				// if pathDivergenceFound && fullPathLength != 64 && fullPathLength != 128 {
				// 	fullDivergingPath := make([]byte, fullPathLength)
				// 	for i := 0; i < int(keyPos+1); i++ {
				// 		fullDivergingPath[i] = hashedKey[i]
				// 	}
				// 	for i := 0; i < len(hashedExtKey); i++ {
				// 		fullDivergingPath[int(keyPos)+1+i] = hashedExtKey[i]
				// 	}
				// 	rowData, err := readBranchData(hph, fullDivergingPath)
				// 	if err != nil {
				// 		return nil, fmt.Errorf("failed to read branchdata: %w", err)
				// 	}
				// 	terminalNode, err := terminalRowToNode(hph, rowData, hph.depths[row])
				// 	if err != nil {
				// 		return nil, fmt.Errorf("failed to parse terminal node: %w", err)
				// 	}
				// 	nextNode = &trie.ShortNode{Key: hashedExtKey, Val: terminalNode}
				// }

				// Val will be set to HashNode with hash of branch node it points to when the current node is processed.
				// Currently necessary, because the commented out code above which reads branch data and converts it
				nextNode = &trie.ShortNode{Key: common.Copy(hashedExtKey)}
			} else {
				keyPos += extKeyLength // jump ahead

				if keyPos+1 == int16(len(hashedKey)) || keyPos+1 == 64 {
					extKeyLength++ //  +1 for the terminator 0x10 ([16])  byte when on a terminal extension node
				}
				extensionKey := make([]byte, extKeyLength)
				copy(extensionKey, hashedExtKey)
				if keyPos+1 == int16(len(hashedKey)) || keyPos+1 == 64 {
					extensionKey[len(extensionKey)-1] = terminatorHexByte // append terminator byte
				}
				nextNode = &trie.ShortNode{Key: extensionKey} // Value will be in the next iteration
				if keyPos+1 == int16(len(hashedKey)) || keyPos+1 == 64 {
					if cellToExpand.storageAddrLen > 0 && !depthAdjusted {
						storageUpdate, err := hph.storageFromCacheOrDB(cellToExpand.storageAddr[:cellToExpand.storageAddrLen])
						if err != nil {
							return nil, err
						}
						storageValueNode := trie.ValueNode(storageUpdate.Storage[:storageUpdate.StorageLen])
						nextNode = &trie.ShortNode{Key: extensionKey, Val: storageValueNode}
					} else if cellToExpand.accountAddrLen > 0 {
						accNode, err := hph.witnessCreateAccountNode(cellToExpand, hph.depths[row], hashedKey, codeReads)
						if err != nil {
							return nil, err
						}
						nextNode = &trie.ShortNode{Key: extensionKey, Val: accNode}
						extNodeSubTrie := trie.NewInMemoryTrie(nextNode)
						subTrieRoot := extNodeSubTrie.Root()
						cellHash, _, _, _ := hph.witnessComputeCellHashWithStorage(cellToExpand, hph.depths[row], nil)
						if !bytes.Equal(subTrieRoot, cellHash[1:]) {
							return nil, fmt.Errorf("subTrieRoot(%x) != cellHash(%x)", subTrieRoot, cellHash[1:])
						}
						extNodeSubTrie.Reset() // clear cached hash refs
						// // DEBUG patch with cell hash which we know to be correct
						//fmt.Printf("witness cell (%d, %0x, depth=%d) %s\n", row, currentNibble, hph.depths[row], cellToExpand.FullString())
						//nextNode = trie.NewHashNode(cellToExpand.stateHash[:])
					}
					if keyPos+1 == int16(len(hashedKey)) {
						keyPos++
					}
				}
			}
		} else if cellToExpand.storageAddrLen > 0 { // storage cell
			storageUpdate, err := hph.storageFromCacheOrDB(cellToExpand.storageAddr[:cellToExpand.storageAddrLen])
			if err != nil {
				return nil, err
			}
			storageValueNode := trie.ValueNode(storageUpdate.Storage[:storageUpdate.StorageLen])
			nextNode = &storageValueNode //nolint:ineffassign, wastedassign
			break
		} else if cellToExpand.accountAddrLen > 0 { // account cell
			accNode, err := hph.witnessCreateAccountNode(cellToExpand, hph.depths[row], hashedKey, codeReads)
			if err != nil {
				return nil, err
			}
			// check if account node was already constructed, then we need to switch to storage
			if _, ok := currentNode.(*trie.AccountNode); ok && len(hashedKey) > 64 {
				if cellToExpand.hashedExtLen > 0 {
					extKey := common.Copy(cellToExpand.hashedExtension[:cellToExpand.hashedExtLen])
					nextNode = &trie.ShortNode{Key: extKey}
				} else {
					nextNode = &trie.FullNode{}
				}
			} else {
				nextNode = accNode
			}
			keyPos++ // only move one nibble
		} else if cellToExpand.hashLen > 0 { // hash cell means we will expand using a full node
			nextNode = &trie.FullNode{}
			keyPos++
		} else if cellToExpand.IsEmpty() {
			nextNode = nil // no more expanding can happen (this could be due )
		} else { // default for now before we handle extLen
			nextNode = &trie.FullNode{}
			keyPos++

			if hph.trace {
				fmt.Printf("[witness] DefaultFullNode cell (%d, %0x, depth=%d) %s %+v\n", row, currentNibble, hph.depths[row], cellToExpand.FullString(), nextNode)
			}
		}

		if hph.trace {
			fmt.Printf("[witness] nextNode (%d, %0x, depth=%d) %T %+v %s keyPos %d\n", row, currentNibble, hph.depths[row], nextNode, nextNode, cellToExpand.FullString(), keyPos)
			fmt.Printf("[witness] currentNode (%d, %0x, depth=%d) %T %+v %s keyPos %d\n", row, currentNibble, hph.depths[row], currentNode, currentNode, cellToExpand.FullString(), keyPos)
		}

		// process the current node
		if fullNode, ok := currentNode.(*trie.FullNode); ok { // handle full node case
			for col := 0; col < 16; col++ {
				currentCell := &hph.grid[row][col]
				if currentCell.IsEmpty() {
					fullNode.Children[col] = nil
					continue
				}
				cellHash, _, _, err := hph.witnessComputeCellHashWithStorage(currentCell, hph.depths[row], nil)
				if err != nil {
					return nil, err
				}
				fullNode.Children[col] = trie.NewHashNode(common.Copy(cellHash[1:])) // because cellHash has 33 bytes and we want 32

				if hph.trace {
					fmt.Printf("[witness, pos %d] FullNodeChild Hash (%d, %0x, depth=%d) %s proof %+v\n", keyPos, row, col, hph.depths[row], currentCell.FullString(), fullNode.Children[col])
				}
			}

			// this deals with the edge case where the extension key in nextNode diverges from hashedKey
			// and points to a branch node. In this case we just need to provide the hash of this branch node
			if pathDivergenceFound {
				terminalCell := &hph.grid[row][currentNibble]
				nextNode.(*trie.ShortNode).Val = trie.NewHashNode(common.Copy(terminalCell.hash[:]))
			}
			fullNode.Children[currentNibble] = nextNode // ready to expand next nibble in the path
		} else if accNode, ok := currentNode.(*trie.AccountNode); ok {
			if len(hashedKey) <= 64 { // no storage, stop here
				nextNode = nil // nolint:ineffassign, wastedassign
				if hph.trace {
					fmt.Printf("[witness] AccountNode (break) (%d, %0x, depth=%d) %s proof %+v\n", row, currentNibble, hph.depths[row], cellToExpand.FullString(), accNode)
				}
				break
			}

			// there is storage so we need to expand further
			accNode.Storage = nextNode
			if hph.trace {
				fmt.Printf("[witness] AccountNode (+storage) (%d, %0x, depth=%d) %s proof %+v\n", row, currentNibble, hph.depths[row], cellToExpand.FullString(), accNode)
			}
		} else if extNode, ok := currentNode.(*trie.ShortNode); ok { // handle extension node case
			extNode.Val = nextNode

			if hph.trace {
				fmt.Printf("[witness, pos %d] ShortNode (%d, %0x, depth=%d) %s proof %+v\n", keyPos, row, currentNibble, hph.depths[row], cellToExpand.FullString(), extNode)
			}
		} else {
			if hph.trace {
				fmt.Printf("[witness] current node is nil (%d, %0x, depth=%d) %s proof %+v\n", row, currentNibble, hph.depths[row], cellToExpand.FullString(), currentNode)
			}
			break // break if currentNode is nil
		}
		if nextShortNode, ok := nextNode.(*trie.ShortNode); ok && len(hashedKey) > 64 && keyPos+1 == 64 {
			// this is because there won't be an account cell stacked in the row below the current row in this case
			// so the account cell will be skipped in the grid in this case
			if nextAccNode, ok := nextShortNode.Val.(*trie.AccountNode); ok {
				if nextAccNode.Root != trie.EmptyRoot {
					nextNode = nextAccNode // only continue traversal if the account has storage
				} else {
					break
				}
				if hph.trace {
					fmt.Printf("[witness] AccountNode (+StorageTrie) (%d, %0x, depth=%d) %s [proof %+v\n", row, currentNibble, hph.depths[row], cellToExpand.FullString(), nextAccNode)
				}
			}
		}
		currentNode = nextNode
	}
	tr := trie.NewInMemoryTrie(rootNode)
	return tr, 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.trace {
		fmt.Printf("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.trace {
			branchData, _ = hph.branchFromCacheOrDB(key)
			fmt.Printf("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
	bitmap := binary.BigEndian.Uint16(branchData[0:])
	pos := 2
	if deleted { // All cells come as deleted (touched but not present after)
		hph.touchMap[row], hph.afterMap[row] = bitmap, 0
	} else {
		hph.touchMap[row], hph.afterMap[row] = 0, bitmap
	}
	//fmt.Printf("unfoldBranchNode prefix '%x' [%x], afterMap = [%016b], touchMap = [%016b]\n", key, branchData, hph.afterMap[row], hph.touchMap[row])
	// Loop iterating over the set bits of modMask
	for bitset, j := bitmap, 0; bitset != 0; j++ {
		bit := bitset & -bitset
		nibble := bits.TrailingZeros16(bit)
		cell := &hph.grid[row][nibble]
		fieldBits := branchData[pos]
		pos++
		if pos, err = cell.fillFromFields(branchData, pos, cellFields(fieldBits)); err != nil {
			return fmt.Errorf("prefix [%x] branchData[%x]: %w", hph.currentKey[:hph.currentKeyLen], branchData, err)
		}
		if hph.trace {
			fmt.Printf("cell (%d, %x, depth=%d) %s\n", row, nibble, depth, cell.FullString())
		}

		// relies on plain account/storage key so need to be dereferenced before hashing
		if err = cell.deriveHashedKeys(depth, hph.keccak, hph.accountKeyLen, hph.cellHashBuf[:]); err != nil {
			return err
		}
		bitset ^= bit
	}
	hph.depths[hph.activeRows] = depth
	hph.activeRows++
	return nil
}

func (hph *HexPatriciaHashed) unfold(hashedKey []byte, unfolding int16) error {
	if hph.trace {
		fmt.Printf("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.trace {
			fmt.Printf("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.trace {
			fmt.Printf("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 := 0; i < 16; i++ {
		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)
	}

	var nibble uint8
	var copyLen int16
	if upCell.hashedExtLen >= unfolding {
		depth = upDepth + unfolding
		nibble = upCell.hashedExtension[unfolding-1]
		copyLen = unfolding - 1
	} else {
		depth = upDepth + upCell.hashedExtLen
		nibble = upCell.hashedExtension[upCell.hashedExtLen-1]
		copyLen = upCell.hashedExtLen - 1
	}

	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, min(unfolding, upCell.hashedExtLen))
	if hph.trace {
		fmt.Printf("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])
}

var (
	hadToLoad   atomic.Uint64
	skippedLoad atomic.Uint64
	hadToReset  atomic.Uint64
)

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

func (hph *HexPatriciaHashed) createCellGetter(b []byte, updateKey []byte, row int, depth int16) func(nibble int, skip bool) (*cell, error) {
	hashBefore := make([]byte, 32) // buffer reused between calls
	return func(nibble int, skip bool) (*cell, error) {
		if skip {
			if _, err := hph.keccak2.Write(b); err != nil {
				return nil, fmt.Errorf("failed to write empty nibble to hash: %w", err)
			}
			if hph.trace {
				fmt.Printf("  %x: empty(%d, %x, depth=%d)\n", nibble, row, nibble, depth)
			}
			return nil, nil
		}
		cell := &hph.grid[row][nibble]
		if cell.accountAddrLen > 0 && cell.stateHashLen == 0 && !cell.loaded.account() && !cell.Deleted() {
			//panic("account not loaded" + fmt.Sprintf("%x", cell.accountAddr[:cell.accountAddrLen]))
			log.Warn("account not loaded", "pref", updateKey, "c", fmt.Sprintf("(%d, %x, depth=%d", row, nibble, depth), "cell", cell.String())
		}
		if cell.storageAddrLen > 0 && cell.stateHashLen == 0 && !cell.loaded.storage() && !cell.Deleted() {
			//panic("storage not loaded" + fmt.Sprintf("%x", cell.storageAddr[:cell.storageAddrLen]))
			log.Warn("storage not loaded", "pref", updateKey, "c", fmt.Sprintf("(%d, %x, depth=%d", row, nibble, depth), "cell", cell.String())
		}

		loadedBefore := cell.loaded
		copy(hashBefore, cell.stateHash[:cell.stateHashLen])
		hashBefore = hashBefore[:cell.stateHashLen]

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

		if dbg.KVReadLevelledMetrics && hashBefore != nil && (cell.accountAddrLen > 0 || cell.storageAddrLen > 0) {
			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 nil, err
		}

		return cell, nil
	}
}

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
	}
}

// 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() (err error) {
	updateKeyLen := hph.currentKeyLen
	if hph.activeRows == 0 {
		return errors.New("cannot fold - no active rows")
	}
	if hph.trace {
		fmt.Printf("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.trace {
			fmt.Printf("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.trace {
			fmt.Printf("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.trace {
		fmt.Printf("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, nibblesLeftAfterUpdate := afterMapUpdateKind(hph.afterMap[row])
	switch updateKind {
	case updateKindDelete: // Everything deleted
		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
			}
		}

		upCell.reset()
		if hph.branchBefore[row] {
			_, err := hph.branchEncoder.CollectUpdate(hph.ctx, updateKey, 0, hph.touchMap[row], 0, RetrieveCellNoop)
			if err != nil {
				return fmt.Errorf("failed to encode leaf node update: %w", err)
			}
			if hph.cache != nil {
				hph.cache.EvictBranch(updateKey)
			}
		}
		hph.activeRows--
		if upDepth > 0 {
			hph.currentKeyLen = upDepth - 1
		} else {
			hph.currentKeyLen = 0
		}
	case updateKindPropagate: // Leaf or extension node
		if hph.touchMap[row] != 0 {
			// any modifications
			if row == 0 {
				hph.rootTouched = true
			} else {
				// Modification is propagated upwards
				hph.touchMap[row-1] |= uint16(1) << nibble
			}
		}
		nibble := bits.TrailingZeros16(hph.afterMap[row])
		cell := &hph.grid[row][nibble]
		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], nibble)

		if hph.branchBefore[row] { // encode Delete if prefix existed before
			//fmt.Printf("delete existed row %d prefix %x\n", row, updateKey)
			_, err := hph.branchEncoder.CollectUpdate(hph.ctx, updateKey, 0, hph.touchMap[row], 0, RetrieveCellNoop)
			if err != nil {
				return fmt.Errorf("failed to encode leaf node update: %w", err)
			}
		}
		hph.activeRows--
		hph.currentKeyLen = max(upDepth-1, 0)
		if hph.trace {
			fmt.Printf("formed leaf (%d %x, depth=%d) [%x] %s\n", row, nibble, depth, updateKey, cell.FullString())
		}
	case updateKindBranch:
		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
		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.trace {
					fmt.Printf("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++
				}
			}
			counters, err = hph.loadStateIfNeeded(cell, counters)
			if err != nil {
				return err
			}
			if dbg.KVReadLevelledMetrics {
				hph.hadToLoadL[hph.depthsToTxNum[depth]] = counters
			}
			/* end of memoization */

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

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

		b := [...]byte{0x80}

		if hph.branchEncoder.DeferUpdatesEnabled() {
			// Deferred path: compute cell hashes and feed keccak2, but defer EncodeBranch
			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(b[:]); 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(b[:]); 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
			}
			// Defer the branch encoding
			if err := hph.branchEncoder.CollectDeferredUpdate(hph.ctx, updateKey, bitmap, hph.touchMap[row], hph.afterMap[row], &hph.grid[row], depth); err != nil {
				return fmt.Errorf("failed to collect deferred branch update: %w", err)
			}
		} else {
			// Normal path: EncodeBranch with cellGetter that feeds keccak2
			cellGetter := hph.createCellGetter(b[:], updateKey, row, depth)
			lastNibble, err := hph.branchEncoder.CollectUpdate(hph.ctx, updateKey, bitmap, hph.touchMap[row], hph.afterMap[row], cellGetter)
			if err != nil {
				return fmt.Errorf("failed to encode branch update: %w", err)
			}
			for i := lastNibble; i < 17; i++ {
				if _, err := hph.keccak2.Write(b[:]); err != nil {
					return err
				}
				if hph.trace {
					fmt.Printf("  %x: empty(%d, %x, depth=%d)\n", i, row, i, depth)
				}
			}
		}
		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
		}
		if hph.trace {
			fmt.Printf("} [%x]\n", upCell.hash[:])
		}
		hph.activeRows--
		if upDepth > 0 {
			hph.currentKeyLen = upDepth - 1
		} else {
			hph.currentKeyLen = 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 skipStat{}, 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 skipStat{}, 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 skipStat{}, nil
}

func (hph *HexPatriciaHashed) deleteCell(hashedKey []byte) {
	if hph.trace {
		fmt.Printf("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.trace {
				fmt.Printf("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.trace {
				fmt.Printf("deleteCell setting (%d, %x)\n", row, nibble)
			}
		} else {
			if hph.trace {
				fmt.Printf("deleteCell ignoring (%d, %x)\n", row, nibble)
			}
		}
	}
	cell.reset()
}

// 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() {
		hph.deleteCell(hashedKey)
		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.trace {
			fmt.Printf("updateCell setting (%d, %x, depth=%d)\n", row, nibble, depth)
		}
	}
	if cell.hashedExtLen == 0 {
		copy(cell.hashedExtension[:], hashedKey[depth:])
		cell.hashedExtLen = int16(len(hashedKey)) - depth
		if hph.trace {
			fmt.Printf("set downHasheKey=[%x]\n", cell.hashedExtension[:cell.hashedExtLen])
		}
	} else {
		if hph.trace {
			fmt.Printf("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.trace {
		fmt.Printf("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
}

func (hph *HexPatriciaHashed) followAndUpdate(hashedKey, plainKey []byte, stateUpdate *Update) (err error) {
	//if hph.trace {
	// fmt.Printf("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 until we step on an empty cell
	for unfolding := hph.needUnfolding(hashedKey); unfolding > 0; unfolding = hph.needUnfolding(hashedKey) {
		printLater := hph.currentKeyLen == 0 && hph.mounted && hph.trace
		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.Printf("[%x] subtrie pref '%x' d=%d\n", hph.mountedNib, hph.currentKey[:hph.currentKeyLen], hph.depths[max(0, hph.activeRows-1)])
		}
		// fmt.Printf("mnt: %0x current: %x path %x\n", hph.mountedNib, hph.currentKey[:hph.currentKeyLen], hashedKey)
	}

	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(nib int) (cell, error) {
	if nib != hph.mountedNib {
		panic(fmt.Sprintf("foldMounted: nib (%x)!= mountedNib (%x)", nib, hph.mountedNib))
	}

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

	for hph.activeRows > 0 {
		// 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] == 1 {
			if hph.trace {
				fmt.Printf("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.trace {
		fmt.Printf("===[%x] !@folded to the root\n", hph.mountedNib)
	}
	if hph.rootPresent && hph.rootTouched {
		if hph.trace {
			fmt.Printf("mount root as %02x %s\n", hph.mountedNib, hph.root.String())
		}
		return hph.root, nil
	}
	if hph.trace {
		fmt.Printf("mount as nibble %02x %s\n", hph.mountedNib, hph.grid[0][hph.mountedNib].String())
	}
	// todo potential bug
	return hph.grid[0][hph.mountedNib], nil
}

// Generate the block witness. This works by loading each key from the list of updates (they are not really updates since we won't modify the trie,
// but currently need to be defined like that for the fold/unfold algorithm) into the grid and traversing the grid to convert it into `triedeprecated.Trie`.
// All the individual tries are combined to create the final witness trie.
// Because the grid is lacking information about the code in smart contract accounts which is also part of the witness, we need to provide that as an input parameter to this function (`codeReads`)
func (hph *HexPatriciaHashed) GenerateWitness(ctx context.Context, updates *Updates, codeReads map[common.Hash]witnesstypes.CodeWithHash, logPrefix string) (witnessTrie *trie.Trie, rootHash []byte, err error) {
	var (
		m  runtime.MemStats
		ki uint64

		updatesCount = updates.Size()
		logEvery     = time.NewTicker(20 * time.Second)
	)
	hph.memoizationOff, hph.trace = true, false
	// defer func() {
	// 	hph.memoizationOff, hph.trace = false, false
	// }()

	defer logEvery.Stop()
	var tries []*trie.Trie = make([]*trie.Trie, 0, len(updates.keys)) // slice of tries, i.e the witness for each key, these will be all merged into single trie
	err = updates.HashSort(ctx, nil, func(hashedKey, plainKey []byte, stateUpdate *Update) error {
		select {
		case <-logEvery.C:
			dbg.ReadMemStats(&m)
			log.Info(fmt.Sprintf("[%s][agg] computing trie", logPrefix),
				"progress", fmt.Sprintf("%s/%s", common.PrettyCounter(ki), common.PrettyCounter(updatesCount)),
				"alloc", common.ByteCount(m.Alloc), "sys", common.ByteCount(m.Sys))
		default:
		}

		var tr *trie.Trie
		if hph.trace {
			// de-nibblize for better printing
			printableHashedKey, _ := CompactKey(hashedKey)
			fmt.Printf("\n%d/%d) witnessing [%x] hashedKey [%x] currentKey [%x]\n", ki+1, updatesCount, plainKey, printableHashedKey, hph.currentKey[:hph.currentKeyLen])
		}

		var update *Update
		if int16(len(plainKey)) == hph.accountKeyLen { // account
			update, err = hph.accountFromCacheOrDB(plainKey)
			if err != nil {
				return fmt.Errorf("account with plainkey=%x not found: %w", plainKey, err)
			}
			if hph.trace {
				addrHash := crypto.HashData(plainKey)
				fmt.Printf("account with plainKey=%x, addrHash=%x FOUND = %v\n", plainKey, addrHash, update)
			}
		} else {
			update, err = hph.storageFromCacheOrDB(plainKey)
			if err != nil {
				return fmt.Errorf("storage with plainkey=%x not found: %w", plainKey, err)
			}
			if hph.trace {
				fmt.Printf("storage found = %v\n", update.Storage[:update.StorageLen])
			}
		}

		// Keep folding until the currentKey is the prefix of the key we modify
		for hph.needFolding(hashedKey) {
			if err := hph.fold(); err != nil {
				return fmt.Errorf("fold: %w", err)
			}
		}
		// Now unfold until we step on an empty cell
		for unfolding := hph.needUnfolding(hashedKey); unfolding > 0; unfolding = hph.needUnfolding(hashedKey) {
			if err := hph.unfold(hashedKey, unfolding); err != nil {
				return fmt.Errorf("unfold: %w", err)
			}
		}
		//hph.PrintGrid()
		//hph.updateCell(plainKey, hashedKey, update)

		// convert grid to trie.Trie
		tr, err = hph.toWitnessTrie(hashedKey, codeReads) // build witness trie for this key, based on the current state of the grid
		if err != nil {
			return err
		}
		tr.Reset()
		tries = append(tries, tr)
		ki++
		return nil
	})

	if err != nil {
		return nil, nil, fmt.Errorf("hash sort failed: %w", err)
	}

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

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

	// merge all individual tries
	witnessTrie, err = trie.MergeTries(tries)
	if err != nil {
		return nil, nil, err
	}

	witnessTrieRootHash := witnessTrie.Root()

	return witnessTrie, witnessTrieRootHash, nil
}

func (hph *HexPatriciaHashed) Process(ctx context.Context, updates *Updates, logPrefix string, progress chan *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.trace = true

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

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

	// Setup warmup if configured
	var warmuper *Warmuper
	if warmup.Enabled {
		warmup.EnableWarmupCache = hph.enableWarmupCache
		warmuper = NewWarmuper(ctx, warmup)
		warmuper.Start()
		defer warmuper.CloseAndWait()
		// Set cache on trie if warmup cache is enabled
		if warmup.EnableWarmupCache {
			hph.cache = warmuper.Cache()
			hph.branchEncoder.SetCache(hph.cache)
			defer func() {
				hph.cache = nil
				hph.branchEncoder.SetCache(nil)
			}()
		} else {
			hph.cache = nil
			hph.branchEncoder.SetCache(nil)
		}
	}

	err = updates.HashSort(ctx, warmuper, func(hashedKey, plainKey []byte, stateUpdate *Update) error {
		select {
		case <-logEvery.C:
			if progress != nil {
				progress <- &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.trace || hph.traceDomain || hph.capture != 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])

			if hph.trace || hph.traceDomain {
				fmt.Println(trace)
			}

			if hph.capture != nil {
				hph.capture = append(hph.capture, trace)
			}
		}

		if err := hph.followAndUpdate(hashedKey, plainKey, stateUpdate); err != nil {
			return fmt.Errorf("followAndUpdate: %w", err)
		}
		ki++
		if progress != nil && ki == updatesCount {
			progress <- &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.trace {
		fmt.Printf("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()
	}

	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)
		}
		sort.Slice(ends, func(i, j int) bool { return ends[i] > ends[j] })
		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) SetTrace(trace bool)           { hph.trace = trace }
func (hph *HexPatriciaHashed) SetTraceDomain(trace bool)     { hph.traceDomain = trace }
func (hph *HexPatriciaHashed) EnableWarmupCache(enable bool) { hph.enableWarmupCache = enable }

func (hph *HexPatriciaHashed) GetCapture(truncate bool) []string {
	capture := hph.capture
	if truncate {
		hph.capture = nil
	}
	return capture
}

func (hph *HexPatriciaHashed) SetCapture(capture []string) { hph.capture = capture }

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

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

// SetDeferBranchUpdates enables or disables deferred branch update collection.
// When enabled, branch updates are collected during fold() and applied at the end of Process().
func (hph *HexPatriciaHashed) SetDeferBranchUpdates(defer_ bool) {
	hph.branchEncoder.SetDeferUpdates(defer_)
}

// 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
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
}

// Cache returns the active warmup cache, or nil if none is set.
func (hph *HexPatriciaHashed) Cache() *WarmupCache {
	return hph.cache
}

// branchFromCacheOrDB reads branch data from cache if available, otherwise from DB.
func (hph *HexPatriciaHashed) branchFromCacheOrDB(key []byte) ([]byte, error) {
	if hph.cache != nil {
		if data, found := hph.cache.GetBranch(key); found {
			return data, nil
		}
	}
	data, _, err := hph.ctx.Branch(key)
	return data, err
}

// accountFromCacheOrDB reads account data from cache if available, otherwise from DB.
func (hph *HexPatriciaHashed) accountFromCacheOrDB(plainKey []byte) (*Update, error) {
	if hph.cache != nil {
		if update, found := hph.cache.GetAccount(plainKey); found {
			return update, nil
		}
	}
	return hph.ctx.Account(plainKey)
}

// storageFromCacheOrDB reads storage data from cache if available, otherwise from DB.
func (hph *HexPatriciaHashed) storageFromCacheOrDB(plainKey []byte) (*Update, error) {
	if hph.cache != nil {
		if update, found := hph.cache.GetStorage(plainKey); found {
			return update, nil
		}
	}
	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 := 0; i < len(s.Depths); i++ {
		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 := 0; i < 64; i++ {
		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 := 0; i < len(s.Depths); i++ {
		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 := 0; i < 64; i++ {
		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
	}
	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
	}
	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 := 0; i < len(hph.depths); i++ {
			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)
		//hph.root.deriveHashedKeys(0, hph.keccak, hph.accountKeyLen)
	}

	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 := len(list) - 1; i >= 0; i-- {
			if list[i] != 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
}
