mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-26 16:36:17 +00:00
Implement the in-memory batch update engine for the NOMT binary merkle trie: - elided.go: ElidedChildren 64-bit bitfield for tracking elided child pages - pageset.go: PageSet interface + MemoryPageSet in-memory implementation - pagewalker.go: PageWalker left-to-right walker with partial compaction - AdvanceAndReplace: replace terminal nodes with sub-tries - AdvanceAndPlaceNode: place pre-computed child page roots - Conclude: finalize walk and return new root + updated pages - compactUp/compactStep: hash upward with leaf/terminator compaction - core/triepos.go: add SharedDepth method needed by PageWalker Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
267 lines
6.6 KiB
Go
267 lines
6.6 KiB
Go
package core
|
|
|
|
// TriePosition tracks a position within the paged binary trie, combining a
|
|
// key path prefix with a node index within the current page.
|
|
type TriePosition struct {
|
|
path [32]byte
|
|
depth uint16
|
|
nodeIndex int
|
|
}
|
|
|
|
// NewTriePosition creates a TriePosition at the root.
|
|
func NewTriePosition() TriePosition {
|
|
return TriePosition{}
|
|
}
|
|
|
|
// TriePositionFromPathAndDepth creates a TriePosition at the given depth
|
|
// within the path. Panics if depth is 0.
|
|
func TriePositionFromPathAndDepth(path KeyPath, depth uint16) TriePosition {
|
|
if depth == 0 {
|
|
panic("triepos: depth must be non-zero")
|
|
}
|
|
if depth > 256 {
|
|
panic("triepos: depth out of range")
|
|
}
|
|
pagePath := lastPagePath(path[:], depth)
|
|
return TriePosition{
|
|
path: path,
|
|
depth: depth,
|
|
nodeIndex: computeNodeIndex(pagePath),
|
|
}
|
|
}
|
|
|
|
// IsRoot reports whether the position is at the root.
|
|
func (p *TriePosition) IsRoot() bool {
|
|
return p.depth == 0
|
|
}
|
|
|
|
// Depth returns the current depth in the trie.
|
|
func (p *TriePosition) Depth() uint16 {
|
|
return p.depth
|
|
}
|
|
|
|
// Path returns the raw 32-byte path.
|
|
func (p *TriePosition) Path() [32]byte {
|
|
return p.path
|
|
}
|
|
|
|
// Bit returns the bit at position n in the path (0 = MSB of byte 0).
|
|
func (p *TriePosition) Bit(n int) bool {
|
|
return (p.path[n/8]>>(7-n%8))&1 == 1
|
|
}
|
|
|
|
// NodeIndex returns the index of the current node within its page.
|
|
func (p *TriePosition) NodeIndex() int {
|
|
return p.nodeIndex
|
|
}
|
|
|
|
// Down moves the position down by 1 bit (left if bit=false, right if bit=true).
|
|
func (p *TriePosition) Down(bit bool) {
|
|
if p.depth == 256 {
|
|
panic("triepos: can't descend past 256 bits")
|
|
}
|
|
if int(p.depth)%PageDepth == 0 {
|
|
// Entering a new page: node index resets.
|
|
if bit {
|
|
p.nodeIndex = 1
|
|
} else {
|
|
p.nodeIndex = 0
|
|
}
|
|
} else {
|
|
left := p.nodeIndex*2 + 2
|
|
if bit {
|
|
p.nodeIndex = left + 1
|
|
} else {
|
|
p.nodeIndex = left
|
|
}
|
|
}
|
|
setBit(&p.path, int(p.depth), bit)
|
|
p.depth++
|
|
}
|
|
|
|
// Up moves the position up by d bits.
|
|
func (p *TriePosition) Up(d uint16) {
|
|
if d > p.depth {
|
|
panic("triepos: can't move up past root")
|
|
}
|
|
newDepth := p.depth - d
|
|
if newDepth == 0 {
|
|
*p = NewTriePosition()
|
|
return
|
|
}
|
|
|
|
prevPageDepth := (int(p.depth) + PageDepth - 1) / PageDepth
|
|
newPageDepth := (int(newDepth) + PageDepth - 1) / PageDepth
|
|
p.depth = newDepth
|
|
|
|
if prevPageDepth == newPageDepth {
|
|
// Same page — walk up parent indices.
|
|
for range d {
|
|
p.nodeIndex = (p.nodeIndex - 2) / 2
|
|
}
|
|
} else {
|
|
// Crossed a page boundary — recompute.
|
|
pagePath := lastPagePath(p.path[:], p.depth)
|
|
p.nodeIndex = computeNodeIndex(pagePath)
|
|
}
|
|
}
|
|
|
|
// Sibling moves to the sibling node. Panics at root.
|
|
func (p *TriePosition) Sibling() {
|
|
if p.depth == 0 {
|
|
panic("triepos: can't sibling at root")
|
|
}
|
|
i := int(p.depth) - 1
|
|
flipBit(&p.path, i)
|
|
p.nodeIndex = siblingIndex(p.nodeIndex)
|
|
}
|
|
|
|
// PeekLastBit returns the last bit of the path. Panics at root.
|
|
func (p *TriePosition) PeekLastBit() bool {
|
|
if p.depth == 0 {
|
|
panic("triepos: can't peek at root")
|
|
}
|
|
return p.Bit(int(p.depth) - 1)
|
|
}
|
|
|
|
// PageID returns the PageID for the page this position lands in.
|
|
// Returns nil if at the root.
|
|
func (p *TriePosition) PageID() *PageID {
|
|
if p.IsRoot() {
|
|
return nil
|
|
}
|
|
|
|
pageID := RootPageID()
|
|
d := int(p.depth)
|
|
// Number of complete 6-bit chunks before the current partial chunk.
|
|
fullChunks := (d - 1) / PageDepth
|
|
for i := range fullChunks {
|
|
childIndex := extractChildIndex(p.path, i*PageDepth)
|
|
pageID, _ = pageID.ChildPageID(childIndex)
|
|
}
|
|
return &pageID
|
|
}
|
|
|
|
// DepthInPage returns the number of bits traversed in the current page (1-6),
|
|
// or 0 if at the root.
|
|
func (p *TriePosition) DepthInPage() int {
|
|
if p.depth == 0 {
|
|
return 0
|
|
}
|
|
d := int(p.depth)
|
|
return d - ((d-1)/PageDepth)*PageDepth
|
|
}
|
|
|
|
// IsFirstLayerInPage reports whether this position is at the top of its page
|
|
// (node index 0 or 1).
|
|
func (p *TriePosition) IsFirstLayerInPage() bool {
|
|
return p.nodeIndex&^1 == 0
|
|
}
|
|
|
|
// ChildNodeIndices returns the left and right child node indices within the
|
|
// page. Panics if not at depth 1-5 within the page.
|
|
func (p *TriePosition) ChildNodeIndices() (left, right int) {
|
|
dip := p.DepthInPage()
|
|
if dip == 0 || dip > PageDepth-1 {
|
|
panic("triepos: child indices out of bounds")
|
|
}
|
|
left = p.nodeIndex*2 + 2
|
|
right = left + 1
|
|
return
|
|
}
|
|
|
|
// ChildPageIndex returns the ChildPageIndex for the current node.
|
|
// Panics if not at the last layer of the page (indices 62-125).
|
|
func (p *TriePosition) ChildPageIndex() uint8 {
|
|
if p.nodeIndex < 62 {
|
|
panic("triepos: not at last layer")
|
|
}
|
|
return uint8(p.nodeIndex - 62)
|
|
}
|
|
|
|
// SiblingIndex returns the index of the sibling node.
|
|
func (p *TriePosition) SiblingIndex() int {
|
|
return siblingIndex(p.nodeIndex)
|
|
}
|
|
|
|
// SharedDepth returns the number of leading path bits shared between two
|
|
// TriePositions, considering only bits up to the shorter depth.
|
|
func (p *TriePosition) SharedDepth(other *TriePosition) int {
|
|
maxBits := min(int(p.depth), int(other.depth))
|
|
for i := range maxBits {
|
|
pBit := (p.path[i/8] >> (7 - i%8)) & 1
|
|
oBit := (other.path[i/8] >> (7 - i%8)) & 1
|
|
if pBit != oBit {
|
|
return i
|
|
}
|
|
}
|
|
return maxBits
|
|
}
|
|
|
|
// --- internal helpers ---
|
|
|
|
// computeNodeIndex converts a page-local bit path to a level-order node index.
|
|
// Formula: (2^depth - 2) + bits_as_uint, where depth is 1-6.
|
|
func computeNodeIndex(pagePath pageBitPath) int {
|
|
depth := pagePath.len
|
|
if depth == 0 {
|
|
return 0
|
|
}
|
|
if depth > PageDepth {
|
|
depth = PageDepth
|
|
}
|
|
return (1 << depth) - 2 + pagePath.asUint(depth)
|
|
}
|
|
|
|
// pageBitPath represents a sub-slice of bits within a path for node indexing.
|
|
type pageBitPath struct {
|
|
path []byte // the full 32-byte key path
|
|
offset int // bit offset where this page's path starts
|
|
len int // number of bits (1-6)
|
|
}
|
|
|
|
// asUint interprets the first `n` bits as an unsigned integer (MSB first).
|
|
func (p pageBitPath) asUint(n int) int {
|
|
var val int
|
|
for i := range n {
|
|
byteIdx := (p.offset + i) / 8
|
|
bitIdx := 7 - (p.offset+i)%8
|
|
bit := int((p.path[byteIdx] >> bitIdx) & 1)
|
|
val = (val << 1) | bit
|
|
}
|
|
return val
|
|
}
|
|
|
|
// lastPagePath extracts the relevant bit path for the current page.
|
|
func lastPagePath(path []byte, depth uint16) pageBitPath {
|
|
d := int(depth)
|
|
prevPageEnd := ((d - 1) / PageDepth) * PageDepth
|
|
return pageBitPath{
|
|
path: path,
|
|
offset: prevPageEnd,
|
|
len: d - prevPageEnd,
|
|
}
|
|
}
|
|
|
|
func setBit(path *[32]byte, idx int, val bool) {
|
|
byteIdx := idx / 8
|
|
bitIdx := uint(7 - idx%8)
|
|
if val {
|
|
path[byteIdx] |= 1 << bitIdx
|
|
} else {
|
|
path[byteIdx] &^= 1 << bitIdx
|
|
}
|
|
}
|
|
|
|
func flipBit(path *[32]byte, idx int) {
|
|
byteIdx := idx / 8
|
|
bitIdx := uint(7 - idx%8)
|
|
path[byteIdx] ^= 1 << bitIdx
|
|
}
|
|
|
|
func siblingIndex(nodeIndex int) int {
|
|
if nodeIndex%2 == 0 {
|
|
return nodeIndex + 1
|
|
}
|
|
return nodeIndex - 1
|
|
}
|