mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
Merge bdb7b64173 into 12eabbd76d
This commit is contained in:
commit
8c73d64e1e
11 changed files with 1806 additions and 50 deletions
|
|
@ -244,29 +244,29 @@ func TestKeyToPath(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "depth 0",
|
name: "depth 0",
|
||||||
depth: 0,
|
depth: 0,
|
||||||
key: []byte{0x80}, // 10000000 in binary
|
key: []byte{0x80}, // 10000000 in binary
|
||||||
expected: []byte{1},
|
expected: []byte{0x01, 1}, // 1-bit value 0x01 + length byte 1
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "depth 7",
|
name: "depth 7",
|
||||||
depth: 7,
|
depth: 7,
|
||||||
key: []byte{0xFF}, // 11111111 in binary
|
key: []byte{0xFF}, // 11111111 in binary
|
||||||
expected: []byte{1, 1, 1, 1, 1, 1, 1, 1},
|
expected: []byte{0xFF, 8}, // 8-bit value 0xFF + length byte 8
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "depth crossing byte boundary",
|
name: "depth crossing byte boundary",
|
||||||
depth: 10,
|
depth: 10,
|
||||||
key: []byte{0xFF, 0x00}, // 11111111 00000000 in binary
|
key: []byte{0xFF, 0x00}, // 11111111 00000000 in binary
|
||||||
expected: []byte{1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
|
expected: []byte{0x07, 0xF8, 11}, // 11-bit value 0x07F8 + length byte 11
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "max valid depth",
|
name: "max valid depth",
|
||||||
depth: StemSize*8 - 1,
|
depth: StemSize*8 - 1,
|
||||||
key: make([]byte, HashSize),
|
key: make([]byte, HashSize),
|
||||||
expected: make([]byte, StemSize*8),
|
expected: append(make([]byte, StemSize), StemSize*8), // 248 bits of zeros + length byte 248
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
605
trie/bintrie/bitarray.go
Normal file
605
trie/bintrie/bitarray.go
Normal file
|
|
@ -0,0 +1,605 @@
|
||||||
|
// Copyright 2026 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
package bintrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxUint64 = uint64(math.MaxUint64) // 0xFFFFFFFFFFFFFFFF
|
||||||
|
maxUint8 = uint8(math.MaxUint8)
|
||||||
|
)
|
||||||
|
|
||||||
|
var emptyBitArray = new(BitArray)
|
||||||
|
|
||||||
|
// BitArray represents a bit array with length representing the number of used bits.
|
||||||
|
// It uses a little endian representation to do bitwise operations of the words efficiently.
|
||||||
|
// For example, if len is 10, it means that the 2^9, 2^8, ..., 2^0 bits are used.
|
||||||
|
// The max length is 255 bits (uint8), because our use case only need up to 248 bits for a given trie key.
|
||||||
|
// Although words can be used to represent 256 bits, we don't want to add an additional byte for the length.
|
||||||
|
type BitArray struct {
|
||||||
|
len uint8 // number of used bits
|
||||||
|
words [4]uint64 // little endian (i.e. words[0] is the least significant)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBitArray creates a new bit array with the given length and value.
|
||||||
|
func NewBitArray(length uint8, val uint64) BitArray {
|
||||||
|
var b BitArray
|
||||||
|
b.SetUint64(length, val)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitArray) Len() uint8 {
|
||||||
|
return b.len
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the bytes representation of the bit array in big endian format
|
||||||
|
func (b *BitArray) Bytes() [32]byte {
|
||||||
|
var res [32]byte
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint64(res[0:8], b.words[3])
|
||||||
|
binary.BigEndian.PutUint64(res[8:16], b.words[2])
|
||||||
|
binary.BigEndian.PutUint64(res[16:24], b.words[1])
|
||||||
|
binary.BigEndian.PutUint64(res[24:32], b.words[0])
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append sets the bit array to the concatenation of x and y and returns the bit array.
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// x = 000 (len=3)
|
||||||
|
// y = 111 (len=3)
|
||||||
|
// Append(x,y) = 000111 (len=6)
|
||||||
|
func (b *BitArray) Append(x, y *BitArray) *BitArray {
|
||||||
|
if x.len == 0 {
|
||||||
|
return b.Set(y)
|
||||||
|
}
|
||||||
|
if y.len == 0 {
|
||||||
|
return b.Set(x)
|
||||||
|
}
|
||||||
|
if x.len > maxUint8-y.len {
|
||||||
|
panic("error on bitarray append: result would exceed maximum length of 255 bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift left by y's length and OR with y
|
||||||
|
return b.lsh(x, y.len).or(b, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendBit sets the bit array to the concatenation of x and a single bit.
|
||||||
|
// Equivalent to Append(x, {bit}) but avoids allocating a temporary BitArray.
|
||||||
|
func (b *BitArray) AppendBit(x *BitArray, bit uint8) *BitArray {
|
||||||
|
if x.len == 0 {
|
||||||
|
return b.SetBit(bit)
|
||||||
|
}
|
||||||
|
b.lsh(x, 1)
|
||||||
|
b.words[0] |= uint64(bit & 1)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// MSBs sets the bit array to the most significant 'n' bits of x, that is position 0 to n (exclusive).
|
||||||
|
// If n >= x.len, the bit array is an exact copy of x.
|
||||||
|
// Think of this method as array[0:n]
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// x = 11001011 (len=8)
|
||||||
|
// MSBs(x, 4) = 1100 (len=4)
|
||||||
|
// MSBs(x, 10) = 11001011 (len=8, original x)
|
||||||
|
// MSBs(x, 0) = 0 (len=0)
|
||||||
|
func (b *BitArray) MSBs(x *BitArray, n uint8) *BitArray {
|
||||||
|
if n >= x.len {
|
||||||
|
return b.Set(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.rsh(x, x.len-n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal checks if two bit arrays are equal
|
||||||
|
func (b *BitArray) Equal(x *BitArray) bool {
|
||||||
|
if b == nil || x == nil {
|
||||||
|
panic("bit array is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.len == x.len && b.words == x.words
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBytes interprets the data as the big-endian bytes, sets the bit array to that value and returns it.
|
||||||
|
// If the data is larger than 32 bytes, only the first 32 bytes are used.
|
||||||
|
func (b *BitArray) SetBytes(length uint8, data []byte) *BitArray {
|
||||||
|
switch l := len(data); l {
|
||||||
|
case 0:
|
||||||
|
b.clear()
|
||||||
|
case 1:
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, 0, uint64(data[0])
|
||||||
|
case 2:
|
||||||
|
_ = data[1]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, 0, uint64(binary.BigEndian.Uint16(data[0:2]))
|
||||||
|
case 3:
|
||||||
|
_ = data[2]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, 0, uint64(binary.BigEndian.Uint16(data[1:3]))|uint64(data[0])<<16
|
||||||
|
case 4:
|
||||||
|
_ = data[3]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, 0, uint64(binary.BigEndian.Uint32(data[0:4]))
|
||||||
|
case 5:
|
||||||
|
_ = data[4]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, 0, bigEndianUint40(data[0:5])
|
||||||
|
case 6:
|
||||||
|
_ = data[5]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, 0, bigEndianUint48(data[0:6])
|
||||||
|
case 7:
|
||||||
|
_ = data[6]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, 0, bigEndianUint56(data[0:7])
|
||||||
|
case 8:
|
||||||
|
_ = data[7]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, 0, binary.BigEndian.Uint64(data[0:8])
|
||||||
|
case 9:
|
||||||
|
_ = data[8]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, uint64(data[0]), binary.BigEndian.Uint64(data[1:9])
|
||||||
|
case 10:
|
||||||
|
_ = data[9]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, uint64(binary.BigEndian.Uint16(data[0:2])), binary.BigEndian.Uint64(data[2:10])
|
||||||
|
case 11:
|
||||||
|
_ = data[10]
|
||||||
|
b.words[3], b.words[2] = 0, 0
|
||||||
|
b.words[1], b.words[0] = uint64(binary.BigEndian.Uint16(data[1:3]))|uint64(data[0])<<16, binary.BigEndian.Uint64(data[3:11])
|
||||||
|
case 12:
|
||||||
|
_ = data[11]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, uint64(binary.BigEndian.Uint32(data[0:4])), binary.BigEndian.Uint64(data[4:12])
|
||||||
|
case 13:
|
||||||
|
_ = data[12]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, bigEndianUint40(data[0:5]), binary.BigEndian.Uint64(data[5:13])
|
||||||
|
case 14:
|
||||||
|
_ = data[13]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, bigEndianUint48(data[0:6]), binary.BigEndian.Uint64(data[6:14])
|
||||||
|
case 15:
|
||||||
|
_ = data[14]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, bigEndianUint56(data[0:7]), binary.BigEndian.Uint64(data[7:15])
|
||||||
|
case 16:
|
||||||
|
_ = data[15]
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, binary.BigEndian.Uint64(data[0:8]), binary.BigEndian.Uint64(data[8:16])
|
||||||
|
case 17:
|
||||||
|
_ = data[16]
|
||||||
|
b.words[3], b.words[2] = 0, uint64(data[0])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[1:9]), binary.BigEndian.Uint64(data[9:17])
|
||||||
|
case 18:
|
||||||
|
_ = data[17]
|
||||||
|
b.words[3], b.words[2] = 0, uint64(binary.BigEndian.Uint16(data[0:2]))
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[2:10]), binary.BigEndian.Uint64(data[10:18])
|
||||||
|
case 19:
|
||||||
|
_ = data[18]
|
||||||
|
b.words[3], b.words[2] = 0, uint64(binary.BigEndian.Uint16(data[1:3]))|uint64(data[0])<<16
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[3:11]), binary.BigEndian.Uint64(data[11:19])
|
||||||
|
case 20:
|
||||||
|
_ = data[19]
|
||||||
|
b.words[3], b.words[2] = 0, uint64(binary.BigEndian.Uint32(data[0:4]))
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[4:12]), binary.BigEndian.Uint64(data[12:20])
|
||||||
|
case 21:
|
||||||
|
_ = data[20]
|
||||||
|
b.words[3], b.words[2] = 0, bigEndianUint40(data[0:5])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[5:13]), binary.BigEndian.Uint64(data[13:21])
|
||||||
|
case 22:
|
||||||
|
_ = data[21]
|
||||||
|
b.words[3], b.words[2] = 0, bigEndianUint48(data[0:6])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[6:14]), binary.BigEndian.Uint64(data[14:22])
|
||||||
|
case 23:
|
||||||
|
_ = data[22]
|
||||||
|
b.words[3], b.words[2] = 0, bigEndianUint56(data[0:7])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[7:15]), binary.BigEndian.Uint64(data[15:23])
|
||||||
|
case 24:
|
||||||
|
_ = data[23]
|
||||||
|
b.words[3], b.words[2] = 0, binary.BigEndian.Uint64(data[0:8])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[8:16]), binary.BigEndian.Uint64(data[16:24])
|
||||||
|
case 25:
|
||||||
|
_ = data[24]
|
||||||
|
b.words[3], b.words[2] = uint64(data[0]), binary.BigEndian.Uint64(data[1:9])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[9:17]), binary.BigEndian.Uint64(data[17:25])
|
||||||
|
case 26:
|
||||||
|
_ = data[25]
|
||||||
|
b.words[3], b.words[2] = uint64(binary.BigEndian.Uint16(data[0:2])), binary.BigEndian.Uint64(data[2:10])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[10:18]), binary.BigEndian.Uint64(data[18:26])
|
||||||
|
case 27:
|
||||||
|
_ = data[26]
|
||||||
|
b.words[3] = uint64(binary.BigEndian.Uint16(data[1:3])) | uint64(data[0])<<16
|
||||||
|
b.words[2] = binary.BigEndian.Uint64(data[3:11])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[11:19]), binary.BigEndian.Uint64(data[19:27])
|
||||||
|
case 28:
|
||||||
|
_ = data[27]
|
||||||
|
b.words[3], b.words[2] = uint64(binary.BigEndian.Uint32(data[0:4])), binary.BigEndian.Uint64(data[4:12])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[12:20]), binary.BigEndian.Uint64(data[20:28])
|
||||||
|
case 29:
|
||||||
|
_ = data[28]
|
||||||
|
b.words[3], b.words[2] = bigEndianUint40(data[0:5]), binary.BigEndian.Uint64(data[5:13])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[13:21]), binary.BigEndian.Uint64(data[21:29])
|
||||||
|
case 30:
|
||||||
|
_ = data[29]
|
||||||
|
b.words[3], b.words[2] = bigEndianUint48(data[0:6]), binary.BigEndian.Uint64(data[6:14])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[14:22]), binary.BigEndian.Uint64(data[22:30])
|
||||||
|
case 31:
|
||||||
|
_ = data[30]
|
||||||
|
b.words[3], b.words[2] = bigEndianUint56(data[0:7]), binary.BigEndian.Uint64(data[7:15])
|
||||||
|
b.words[1], b.words[0] = binary.BigEndian.Uint64(data[15:23]), binary.BigEndian.Uint64(data[23:31])
|
||||||
|
default:
|
||||||
|
b.setBytes32(data)
|
||||||
|
}
|
||||||
|
b.len = length
|
||||||
|
b.truncateToLength()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUint64 sets the bit array to the uint64 representation of a bit array.
|
||||||
|
func (b *BitArray) SetUint64(length uint8, data uint64) *BitArray {
|
||||||
|
b.words[0] = data
|
||||||
|
b.words[1], b.words[2], b.words[3] = 0, 0, 0
|
||||||
|
b.len = length
|
||||||
|
b.truncateToLength()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBit sets the bit array to a single bit.
|
||||||
|
func (b *BitArray) SetBit(bit uint8) *BitArray {
|
||||||
|
b.len = 1
|
||||||
|
b.words[0] = uint64(bit & 1)
|
||||||
|
b.words[1], b.words[2], b.words[3] = 0, 0, 0
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a deep copy of the bit array.
|
||||||
|
func (b *BitArray) Copy() BitArray {
|
||||||
|
var res BitArray
|
||||||
|
res.Set(b)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the bit array.
|
||||||
|
// This is typically used for logging or debugging.
|
||||||
|
func (b *BitArray) String() string {
|
||||||
|
bt := b.Bytes()
|
||||||
|
return fmt.Sprintf("(%d) %s", b.len, hex.EncodeToString(bt[:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bit returns the bit value at position n, where n = 0 is MSB.
|
||||||
|
// If n is out of bounds, returns 0.
|
||||||
|
func (b *BitArray) Bit(n uint8) uint8 {
|
||||||
|
if n >= b.Len() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.bitFromLSB(b.Len() - n - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the bit array to the same value as x.
|
||||||
|
func (b *BitArray) Set(x *BitArray) *BitArray {
|
||||||
|
b.len = x.len
|
||||||
|
b.words[0] = x.words[0]
|
||||||
|
b.words[1] = x.words[1]
|
||||||
|
b.words[2] = x.words[2]
|
||||||
|
b.words[3] = x.words[3]
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyBytes returns the path-to-DB-key encoding: the active bytes in big-endian
|
||||||
|
// order followed by a single trailing byte holding the bit-length. The trailing
|
||||||
|
// length disambiguates paths whose active bytes coincide (e.g. 1-bit "1" and
|
||||||
|
// 8-bit "00000001" both pack to integer value 1, but their key encodings are
|
||||||
|
// [0x01, 0x01] and [0x01, 0x08] respectively).
|
||||||
|
//
|
||||||
|
// The empty path is encoded as no bytes: byteCount=0 is unique to len=0, so
|
||||||
|
// no disambiguation byte is needed.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// len = 10, words = [0x3FF, 0, 0, 0] -> [0x03, 0xFF, 0x0A]
|
||||||
|
func (b *BitArray) KeyBytes() []byte {
|
||||||
|
if b.len == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bc := b.byteCount()
|
||||||
|
res := make([]byte, bc+1)
|
||||||
|
wordsBytes := b.Bytes()
|
||||||
|
copy(res[:bc], wordsBytes[32-bc:])
|
||||||
|
res[bc] = b.len
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutKeyBytes writes the key encoding (active bytes followed by length byte)
|
||||||
|
// into dst and returns the populated sub-slice. The empty path returns dst[:0]
|
||||||
|
// without touching dst. For non-empty paths dst must have len >= 33 (32 packed
|
||||||
|
// bytes for 248 bits + 1 length byte).
|
||||||
|
func (b *BitArray) PutKeyBytes(dst []byte) []byte {
|
||||||
|
if b.len == 0 {
|
||||||
|
return dst[:0]
|
||||||
|
}
|
||||||
|
_ = dst[32] // bounds check hint
|
||||||
|
binary.BigEndian.PutUint64(dst[0:8], b.words[3])
|
||||||
|
binary.BigEndian.PutUint64(dst[8:16], b.words[2])
|
||||||
|
binary.BigEndian.PutUint64(dst[16:24], b.words[1])
|
||||||
|
binary.BigEndian.PutUint64(dst[24:32], b.words[0])
|
||||||
|
bc := b.byteCount()
|
||||||
|
copy(dst, dst[32-bc:32])
|
||||||
|
dst[bc] = b.len
|
||||||
|
return dst[:bc+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitFromLSB returns the bit value at position n, where n = 0 is LSB.
|
||||||
|
// If n is out of bounds, returns 0.
|
||||||
|
func (b *BitArray) bitFromLSB(n uint8) uint8 {
|
||||||
|
if n >= b.len {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.words[n/64] & (1 << (n % 64))) != 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyLsb sets the bit array to the least significant 'n' bits of x.
|
||||||
|
// n is counted from the least significant bit, starting at 0.
|
||||||
|
// If length >= x.len, the bit array is an exact copy of x.
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// x = 11001011 (len=8)
|
||||||
|
// copyLsb(x, 4) = 1011 (len=4)
|
||||||
|
// copyLsb(x, 10) = 11001011 (len=8, original x)
|
||||||
|
// copyLsb(x, 0) = 0 (len=0)
|
||||||
|
func (b *BitArray) copyLsb(x *BitArray, n uint8) *BitArray {
|
||||||
|
if n >= x.len {
|
||||||
|
return b.Set(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.len = n
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case n == 0:
|
||||||
|
b.words = [4]uint64{0, 0, 0, 0}
|
||||||
|
case n <= 64:
|
||||||
|
b.words[0] = x.words[0] & (maxUint64 >> (64 - n))
|
||||||
|
b.words[1], b.words[2], b.words[3] = 0, 0, 0
|
||||||
|
case n <= 128:
|
||||||
|
b.words[0] = x.words[0]
|
||||||
|
b.words[1] = x.words[1] & (maxUint64 >> (128 - n))
|
||||||
|
b.words[2], b.words[3] = 0, 0
|
||||||
|
case n <= 192:
|
||||||
|
b.words[0] = x.words[0]
|
||||||
|
b.words[1] = x.words[1]
|
||||||
|
b.words[2] = x.words[2] & (maxUint64 >> (192 - n))
|
||||||
|
b.words[3] = 0
|
||||||
|
default:
|
||||||
|
b.words[0] = x.words[0]
|
||||||
|
b.words[1] = x.words[1]
|
||||||
|
b.words[2] = x.words[2]
|
||||||
|
b.words[3] = x.words[3] & (maxUint64 >> (256 - uint16(n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// lsb returns the least significant bits of `x` with `n` counted from the most significant bit, starting at 0.
|
||||||
|
// Think of this method as array[n:]
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// x = 11001011 (len=8)
|
||||||
|
// lsb(x, 1) = 1001011 (len=7)
|
||||||
|
// lsb(x, 10) = 0 (len=0)
|
||||||
|
// lsb(x, 0) = 11001011 (len=8, original x)
|
||||||
|
func (b *BitArray) lsb(x *BitArray, n uint8) *BitArray {
|
||||||
|
if n == 0 {
|
||||||
|
return b.Set(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > x.Len() {
|
||||||
|
return b.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.copyLsb(x, x.Len()-n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// or sets the bit array to x | y and returns the bit array.
|
||||||
|
func (b *BitArray) or(x, y *BitArray) *BitArray {
|
||||||
|
b.words[0] = x.words[0] | y.words[0]
|
||||||
|
b.words[1] = x.words[1] | y.words[1]
|
||||||
|
b.words[2] = x.words[2] | y.words[2]
|
||||||
|
b.words[3] = x.words[3] | y.words[3]
|
||||||
|
b.len = x.len
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// rsh sets the bit array to x >> n and returns the bit array.
|
||||||
|
func (b *BitArray) rsh(x *BitArray, n uint8) *BitArray {
|
||||||
|
if x.len == 0 {
|
||||||
|
return b.Set(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n >= x.len {
|
||||||
|
return b.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case n == 0:
|
||||||
|
return b.Set(x)
|
||||||
|
case n >= 192:
|
||||||
|
b.rsh192(x)
|
||||||
|
b.len = x.len - n
|
||||||
|
n -= 192
|
||||||
|
b.words[0] >>= n
|
||||||
|
case n >= 128:
|
||||||
|
b.rsh128(x)
|
||||||
|
b.len = x.len - n
|
||||||
|
n -= 128
|
||||||
|
b.words[0] = (b.words[0] >> n) | (b.words[1] << (64 - n))
|
||||||
|
b.words[1] >>= n
|
||||||
|
case n >= 64:
|
||||||
|
b.rsh64(x)
|
||||||
|
b.len = x.len - n
|
||||||
|
n -= 64
|
||||||
|
b.words[0] = (b.words[0] >> n) | (b.words[1] << (64 - n))
|
||||||
|
b.words[1] = (b.words[1] >> n) | (b.words[2] << (64 - n))
|
||||||
|
b.words[2] >>= n
|
||||||
|
default:
|
||||||
|
b.Set(x)
|
||||||
|
b.len -= n
|
||||||
|
b.words[0] = (b.words[0] >> n) | (b.words[1] << (64 - n))
|
||||||
|
b.words[1] = (b.words[1] >> n) | (b.words[2] << (64 - n))
|
||||||
|
b.words[2] = (b.words[2] >> n) | (b.words[3] << (64 - n))
|
||||||
|
b.words[3] >>= n
|
||||||
|
}
|
||||||
|
|
||||||
|
b.truncateToLength()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// lsh sets the bit array to x << n and returns the bit array.
|
||||||
|
func (b *BitArray) lsh(x *BitArray, n uint8) *BitArray {
|
||||||
|
if x.len == 0 || n == 0 {
|
||||||
|
return b.Set(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the result will overflow, we set the length to the max length
|
||||||
|
// but we still shift `n` bits
|
||||||
|
if n > maxUint8-x.len {
|
||||||
|
b.len = maxUint8
|
||||||
|
} else {
|
||||||
|
b.len = x.len + n
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case n >= 192:
|
||||||
|
b.lsh192(x)
|
||||||
|
n -= 192
|
||||||
|
b.words[3] <<= n
|
||||||
|
case n >= 128:
|
||||||
|
b.lsh128(x)
|
||||||
|
n -= 128
|
||||||
|
b.words[3] = (b.words[3] << n) | (b.words[2] >> (64 - n))
|
||||||
|
b.words[2] <<= n
|
||||||
|
case n >= 64:
|
||||||
|
b.lsh64(x)
|
||||||
|
n -= 64
|
||||||
|
b.words[3] = (b.words[3] << n) | (b.words[2] >> (64 - n))
|
||||||
|
b.words[2] = (b.words[2] << n) | (b.words[1] >> (64 - n))
|
||||||
|
b.words[1] <<= n
|
||||||
|
default:
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = x.words[3], x.words[2], x.words[1], x.words[0]
|
||||||
|
b.words[3] = (b.words[3] << n) | (b.words[2] >> (64 - n))
|
||||||
|
b.words[2] = (b.words[2] << n) | (b.words[1] >> (64 - n))
|
||||||
|
b.words[1] = (b.words[1] << n) | (b.words[0] >> (64 - n))
|
||||||
|
b.words[0] <<= n
|
||||||
|
}
|
||||||
|
|
||||||
|
b.truncateToLength()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitArray) setBytes32(data []byte) {
|
||||||
|
_ = data[31] // bound check hint, see https://golang.org/issue/14808
|
||||||
|
b.words[3] = binary.BigEndian.Uint64(data[0:8])
|
||||||
|
b.words[2] = binary.BigEndian.Uint64(data[8:16])
|
||||||
|
b.words[1] = binary.BigEndian.Uint64(data[16:24])
|
||||||
|
b.words[0] = binary.BigEndian.Uint64(data[24:32])
|
||||||
|
}
|
||||||
|
|
||||||
|
// byteCount returns the minimum number of bytes needed to represent the bit array.
|
||||||
|
// It rounds up to the nearest byte.
|
||||||
|
func (b *BitArray) byteCount() uint {
|
||||||
|
const bits8 = 8
|
||||||
|
return (uint(b.len) + (bits8 - 1)) / uint(bits8)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitArray) rsh64(x *BitArray) {
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, x.words[3], x.words[2], x.words[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitArray) rsh128(x *BitArray) {
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, x.words[3], x.words[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitArray) rsh192(x *BitArray) {
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = 0, 0, 0, x.words[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitArray) lsh64(x *BitArray) {
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = x.words[2], x.words[1], x.words[0], 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitArray) lsh128(x *BitArray) {
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = x.words[1], x.words[0], 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitArray) lsh192(x *BitArray) {
|
||||||
|
b.words[3], b.words[2], b.words[1], b.words[0] = x.words[0], 0, 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitArray) clear() *BitArray {
|
||||||
|
b.len = 0
|
||||||
|
b.words[0], b.words[1], b.words[2], b.words[3] = 0, 0, 0, 0
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncateToLength truncates the bit array to the specified length, ensuring that any unused bits are all zeros.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// b := &BitArray{
|
||||||
|
// len: 5,
|
||||||
|
// words: [4]uint64{
|
||||||
|
// 0xFFFFFFFFFFFFFFFF, // Before: all bits are 1
|
||||||
|
// 0x0, 0x0, 0x0,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// b.truncateToLength()
|
||||||
|
// // After: only first 5 bits remain
|
||||||
|
// // words[0] = 0x000000000000001F
|
||||||
|
// // words[1..3] = 0x0
|
||||||
|
func (b *BitArray) truncateToLength() {
|
||||||
|
switch {
|
||||||
|
case b.len == 0:
|
||||||
|
b.words = [4]uint64{0, 0, 0, 0}
|
||||||
|
case b.len <= 64:
|
||||||
|
b.words[0] &= maxUint64 >> (64 - b.len)
|
||||||
|
b.words[1], b.words[2], b.words[3] = 0, 0, 0
|
||||||
|
case b.len <= 128:
|
||||||
|
b.words[1] &= maxUint64 >> (128 - b.len)
|
||||||
|
b.words[2], b.words[3] = 0, 0
|
||||||
|
case b.len <= 192:
|
||||||
|
b.words[2] &= maxUint64 >> (192 - b.len)
|
||||||
|
b.words[3] = 0
|
||||||
|
default:
|
||||||
|
b.words[3] &= maxUint64 >> (256 - uint16(b.len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bigEndianUint40(b []byte) uint64 {
|
||||||
|
_ = b[4] // bounds check hint to compiler; see golang.org/issue/14808
|
||||||
|
return uint64(b[4]) | uint64(b[3])<<8 | uint64(b[2])<<16 | uint64(b[1])<<24 |
|
||||||
|
uint64(b[0])<<32
|
||||||
|
}
|
||||||
|
|
||||||
|
func bigEndianUint48(b []byte) uint64 {
|
||||||
|
_ = b[5] // bounds check hint to compiler; see golang.org/issue/14808
|
||||||
|
return uint64(b[5]) | uint64(b[4])<<8 | uint64(b[3])<<16 | uint64(b[2])<<24 |
|
||||||
|
uint64(b[1])<<32 | uint64(b[0])<<40
|
||||||
|
}
|
||||||
|
|
||||||
|
func bigEndianUint56(b []byte) uint64 {
|
||||||
|
_ = b[6] // bounds check hint to compiler; see golang.org/issue/14808
|
||||||
|
return uint64(b[6]) | uint64(b[5])<<8 | uint64(b[4])<<16 | uint64(b[3])<<24 |
|
||||||
|
uint64(b[2])<<32 | uint64(b[1])<<40 | uint64(b[0])<<48
|
||||||
|
}
|
||||||
1078
trie/bintrie/bitarray_test.go
Normal file
1078
trie/bintrie/bitarray_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -26,12 +26,10 @@ func keyToPath(depth int, key []byte) ([]byte, error) {
|
||||||
if depth >= 31*8 {
|
if depth >= 31*8 {
|
||||||
return nil, errors.New("node too deep")
|
return nil, errors.New("node too deep")
|
||||||
}
|
}
|
||||||
path := make([]byte, 0, depth+1)
|
keyLen := min(len(key), 31)
|
||||||
for i := range depth + 1 {
|
ba := new(BitArray).SetBytes(uint8(keyLen*8), key[:keyLen])
|
||||||
bit := key[i/8] >> (7 - (i % 8)) & 1
|
path := new(BitArray).MSBs(ba, uint8(depth+1))
|
||||||
path = append(path, bit)
|
return path.KeyBytes(), nil
|
||||||
}
|
|
||||||
return path, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invariant: dirty=false implies mustRecompute=false. Every mutation that
|
// Invariant: dirty=false implies mustRecompute=false. Every mutation that
|
||||||
|
|
|
||||||
|
|
@ -283,14 +283,13 @@ func TestInternalNodeCollectNodes(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var collectedPaths [][]byte
|
var collectedPaths []BitArray
|
||||||
flushFn := func(path []byte, hash common.Hash, serialized []byte) {
|
flushFn := func(path BitArray, hash common.Hash, serialized []byte) {
|
||||||
pathCopy := make([]byte, len(path))
|
collectedPaths = append(collectedPaths, path)
|
||||||
copy(pathCopy, path)
|
|
||||||
collectedPaths = append(collectedPaths, pathCopy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.collectNodes(s.root, []byte{1}, flushFn, 8)
|
initialPath := NewBitArray(1, 1)
|
||||||
|
s.collectNodes(s.root, initialPath, flushFn, 8)
|
||||||
|
|
||||||
// Should have collected 3 nodes: left stem, right stem, and the internal node itself
|
// Should have collected 3 nodes: left stem, right stem, and the internal node itself
|
||||||
if len(collectedPaths) != 3 {
|
if len(collectedPaths) != 3 {
|
||||||
|
|
|
||||||
|
|
@ -188,20 +188,20 @@ func (it *binaryNodeIterator) Parent() common.Hash {
|
||||||
return it.store.computeHash(it.stack[len(it.stack)-2].Node)
|
return it.store.computeHash(it.stack[len(it.stack)-2].Node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path returns the bit-path to the current node.
|
// Path returns the bit-packed path to the current node.
|
||||||
// Callers must not retain references to the returned slice after calling Next.
|
// Callers must not retain references to the returned slice after calling Next.
|
||||||
func (it *binaryNodeIterator) Path() []byte {
|
func (it *binaryNodeIterator) Path() []byte {
|
||||||
if it.Leaf() {
|
if it.Leaf() {
|
||||||
return it.LeafKey()
|
return it.LeafKey()
|
||||||
}
|
}
|
||||||
var path []byte
|
var path BitArray
|
||||||
for i, state := range it.stack {
|
for i, state := range it.stack {
|
||||||
if i >= len(it.stack)-1 {
|
if i >= len(it.stack)-1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
path = append(path, byte(state.Index))
|
path.AppendBit(&path, uint8(state.Index))
|
||||||
}
|
}
|
||||||
return path
|
return path.KeyBytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *binaryNodeIterator) NodeBlob() []byte {
|
func (it *binaryNodeIterator) NodeBlob() []byte {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,16 @@ type nodeStore struct {
|
||||||
// stem-split keeps the old stem at a deeper position), so they don't
|
// stem-split keeps the old stem at a deeper position), so they don't
|
||||||
// have free lists.
|
// have free lists.
|
||||||
freeHashed []uint32
|
freeHashed []uint32
|
||||||
|
|
||||||
|
// groupDepth, when > 0, makes hashInternal compute the same hash that
|
||||||
|
// would be produced by serializing the node to a group blob and
|
||||||
|
// recursively hashing the blob's bottom-layer leaves. This matches the
|
||||||
|
// hash a fresh reader would compute via deserializeSubtree, keeping the
|
||||||
|
// parent-stored child hash byte-equal to the child's read-back hash.
|
||||||
|
// When 0, hashInternal falls back to the natural-depth SHA256 recursion
|
||||||
|
// used by tests that construct nodeStore directly without going through
|
||||||
|
// NewBinaryTrie.
|
||||||
|
groupDepth int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNodeStore() *nodeStore {
|
func newNodeStore() *nodeStore {
|
||||||
|
|
|
||||||
|
|
@ -313,14 +313,13 @@ func TestStemNodeCollectNodes(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var collectedPaths [][]byte
|
var collectedPaths []BitArray
|
||||||
flushFn := func(path []byte, hash common.Hash, serialized []byte) {
|
flushFn := func(path BitArray, hash common.Hash, serialized []byte) {
|
||||||
pathCopy := make([]byte, len(path))
|
collectedPaths = append(collectedPaths, path)
|
||||||
copy(pathCopy, path)
|
|
||||||
collectedPaths = append(collectedPaths, pathCopy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.collectNodes(s.root, []byte{0, 1, 0}, flushFn, 8)
|
initialPath := NewBitArray(3, 0b010)
|
||||||
|
s.collectNodes(s.root, initialPath, flushFn, 8)
|
||||||
|
|
||||||
// Should have collected one node (itself)
|
// Should have collected one node (itself)
|
||||||
if len(collectedPaths) != 1 {
|
if len(collectedPaths) != 1 {
|
||||||
|
|
@ -328,7 +327,8 @@ func TestStemNodeCollectNodes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the path
|
// Check the path
|
||||||
if !bytes.Equal(collectedPaths[0], []byte{0, 1, 0}) {
|
expectedPath := NewBitArray(3, 0b010)
|
||||||
t.Errorf("Path mismatch: expected [0, 1, 0], got %v", collectedPaths[0])
|
if !collectedPaths[0].Equal(&expectedPath) {
|
||||||
|
t.Errorf("Path mismatch: expected %v, got %v", expectedPath, collectedPaths[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nodeFlushFn func(path []byte, hash common.Hash, serialized []byte)
|
type nodeFlushFn func(path BitArray, hash common.Hash, serialized []byte)
|
||||||
|
|
||||||
func (s *nodeStore) Hash() common.Hash {
|
func (s *nodeStore) Hash() common.Hash {
|
||||||
return s.computeHash(s.root)
|
return s.computeHash(s.root)
|
||||||
|
|
@ -59,12 +59,30 @@ var parallelHashDepth = min(bits.Len(uint(runtime.NumCPU())), 8)
|
||||||
// goroutine while the right subtree is hashed inline, then the two digests
|
// goroutine while the right subtree is hashed inline, then the two digests
|
||||||
// are combined. Below that threshold the goroutine spawn cost outweighs the
|
// are combined. Below that threshold the goroutine spawn cost outweighs the
|
||||||
// hashing work, so deeper nodes hash both children sequentially.
|
// hashing work, so deeper nodes hash both children sequentially.
|
||||||
|
//
|
||||||
|
// At a group boundary (depth % groupDepth == 0, with groupDepth > 0) the
|
||||||
|
// hash is computed from the group's bottom-layer slot hashes via the same
|
||||||
|
// serialize-then-recursive-hash that a fresh reader applies after reading
|
||||||
|
// the node's blob from disk. This guarantees the parent's stored child
|
||||||
|
// hash equals the child's read-back hash byte-for-byte, regardless of
|
||||||
|
// whether the in-memory subtree placed its stems at natural depth (via
|
||||||
|
// UpdateStem split) or extended depth (via deserializeSubtree).
|
||||||
func (s *nodeStore) hashInternal(idx uint32) common.Hash {
|
func (s *nodeStore) hashInternal(idx uint32) common.Hash {
|
||||||
node := s.getInternal(idx)
|
node := s.getInternal(idx)
|
||||||
if !node.mustRecompute {
|
if !node.mustRecompute {
|
||||||
return node.hash
|
return node.hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.groupDepth > 0 && int(node.depth)%s.groupDepth == 0 {
|
||||||
|
bitmapSize := bitmapSizeForDepth(s.groupDepth)
|
||||||
|
bitmap := make([]byte, bitmapSize)
|
||||||
|
var hashes []common.Hash
|
||||||
|
s.serializeSubtree(makeRef(kindInternal, idx), s.groupDepth, 0, int(node.depth), bitmap, &hashes)
|
||||||
|
node.hash = groupedRecursiveHash(s.groupDepth, bitmap, hashes)
|
||||||
|
node.mustRecompute = false
|
||||||
|
return node.hash
|
||||||
|
}
|
||||||
|
|
||||||
if int(node.depth) < parallelHashDepth {
|
if int(node.depth) < parallelHashDepth {
|
||||||
var input [64]byte
|
var input [64]byte
|
||||||
var lh common.Hash
|
var lh common.Hash
|
||||||
|
|
@ -107,6 +125,43 @@ func (s *nodeStore) hashInternal(idx uint32) common.Hash {
|
||||||
return node.hash
|
return node.hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// groupedRecursiveHash computes the recursive SHA256 hash of a group-blob
|
||||||
|
// subtree, given the bitmap and present-hash list produced by serializeSubtree.
|
||||||
|
//
|
||||||
|
// The output is byte-equal to what hashInternal would compute on a tree
|
||||||
|
// produced by deserializeSubtree reading the same (bitmap, hashes) — i.e.,
|
||||||
|
// it's the hash the fresh-reader path produces. Use this from hashInternal
|
||||||
|
// at group-boundary depths so the parent's stored child hash matches the
|
||||||
|
// child's read-back hash regardless of in-memory stem placement.
|
||||||
|
func groupedRecursiveHash(groupDepth int, bitmap []byte, hashes []common.Hash) common.Hash {
|
||||||
|
nSlots := 1 << groupDepth
|
||||||
|
leaves := make([]common.Hash, nSlots)
|
||||||
|
hashIdx := 0
|
||||||
|
for i := 0; i < nSlots; i++ {
|
||||||
|
if bitmap[i/8]>>(7-(i%8))&1 == 1 {
|
||||||
|
leaves[i] = hashes[hashIdx]
|
||||||
|
hashIdx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
level := leaves
|
||||||
|
var zero common.Hash
|
||||||
|
for len(level) > 1 {
|
||||||
|
next := make([]common.Hash, len(level)/2)
|
||||||
|
for i := 0; i < len(next); i++ {
|
||||||
|
l, r := level[2*i], level[2*i+1]
|
||||||
|
if l == zero && r == zero {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var buf [64]byte
|
||||||
|
copy(buf[:32], l[:])
|
||||||
|
copy(buf[32:], r[:])
|
||||||
|
next[i] = sha256.Sum256(buf[:])
|
||||||
|
}
|
||||||
|
level = next
|
||||||
|
}
|
||||||
|
return level[0]
|
||||||
|
}
|
||||||
|
|
||||||
// serializeSubtree recursively collects child hashes from a subtree of InternalNodes.
|
// serializeSubtree recursively collects child hashes from a subtree of InternalNodes.
|
||||||
// It traverses up to `remainingDepth` levels, storing hashes of bottom-layer children.
|
// It traverses up to `remainingDepth` levels, storing hashes of bottom-layer children.
|
||||||
// position tracks the current index (0 to 2^groupDepth - 1) for bitmap placement.
|
// position tracks the current index (0 to 2^groupDepth - 1) for bitmap placement.
|
||||||
|
|
@ -340,7 +395,10 @@ func (s *nodeStore) decodeNode(serialized []byte, depth int, hn common.Hash, mus
|
||||||
// CollectNodes flushes every node that needs flushing via flushfn in post-order.
|
// CollectNodes flushes every node that needs flushing via flushfn in post-order.
|
||||||
// Invariant: any ancestor of a node that needs flushing is itself marked, so a
|
// Invariant: any ancestor of a node that needs flushing is itself marked, so a
|
||||||
// clean root means the whole subtree is clean.
|
// clean root means the whole subtree is clean.
|
||||||
func (s *nodeStore) collectNodes(ref nodeRef, path []byte, flushfn nodeFlushFn, groupDepth int) {
|
//
|
||||||
|
// BitArray is passed by value (33 bytes) to keep child paths on the stack.
|
||||||
|
// Passing by pointer causes escape to heap per recursive call.
|
||||||
|
func (s *nodeStore) collectNodes(ref nodeRef, path BitArray, flushfn nodeFlushFn, groupDepth int) {
|
||||||
switch ref.Kind() {
|
switch ref.Kind() {
|
||||||
case kindInternal:
|
case kindInternal:
|
||||||
node := s.getInternal(ref.Index())
|
node := s.getInternal(ref.Index())
|
||||||
|
|
@ -375,7 +433,7 @@ func (s *nodeStore) collectNodes(ref nodeRef, path []byte, flushfn nodeFlushFn,
|
||||||
// collectChildGroups traverses within a group to find and collect nodes in the next group.
|
// collectChildGroups traverses within a group to find and collect nodes in the next group.
|
||||||
// remainingLevels is how many more levels below the current node until we reach the group boundary.
|
// remainingLevels is how many more levels below the current node until we reach the group boundary.
|
||||||
// When remainingLevels=0, the current node's children are at the next group boundary.
|
// When remainingLevels=0, the current node's children are at the next group boundary.
|
||||||
func (s *nodeStore) collectChildGroups(node *InternalNode, path []byte, flushfn nodeFlushFn, groupDepth int, remainingLevels int) error {
|
func (s *nodeStore) collectChildGroups(node *InternalNode, path BitArray, flushfn nodeFlushFn, groupDepth int, remainingLevels int) error {
|
||||||
if remainingLevels == 0 {
|
if remainingLevels == 0 {
|
||||||
// Current node is at depth (groupBoundary - 1), its children are at the next group boundary
|
// Current node is at depth (groupBoundary - 1), its children are at the next group boundary
|
||||||
if !node.left.IsEmpty() {
|
if !node.left.IsEmpty() {
|
||||||
|
|
@ -418,32 +476,32 @@ func (s *nodeStore) collectChildGroups(node *InternalNode, path []byte, flushfn
|
||||||
// matching the projection done by serializeSubtree. For StemNodes, the path
|
// matching the projection done by serializeSubtree. For StemNodes, the path
|
||||||
// is extended using the stem's key bits (same as serializeSubtree). For other
|
// is extended using the stem's key bits (same as serializeSubtree). For other
|
||||||
// node types, the path is extended with all-zero (left) bits.
|
// node types, the path is extended with all-zero (left) bits.
|
||||||
func (s *nodeStore) extendPathToGroupLeaf(path []byte, node nodeRef, remainingLevels int) []byte {
|
func (s *nodeStore) extendPathToGroupLeaf(path BitArray, node nodeRef, remainingLevels int) BitArray {
|
||||||
if remainingLevels <= 0 {
|
if remainingLevels <= 0 {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
if node.Kind() == kindStem {
|
if node.Kind() == kindStem {
|
||||||
sn := s.getStem(node.Index())
|
sn := s.getStem(node.Index())
|
||||||
for _ = range remainingLevels {
|
for range remainingLevels {
|
||||||
bit := sn.Stem[len(path)/8] >> (7 - (len(path) % 8)) & 1
|
n := path.Len()
|
||||||
|
bit := sn.Stem[n/8] >> (7 - (n % 8)) & 1
|
||||||
path = appendBit(path, bit)
|
path = appendBit(path, bit)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// HashedNode or other: all-left extension (matches serializeSubtree's
|
// HashedNode or other: all-left extension (matches serializeSubtree's
|
||||||
// position << remainingDepth behavior).
|
// position << remainingDepth behavior).
|
||||||
for _ = range remainingLevels {
|
for range remainingLevels {
|
||||||
path = appendBit(path, 0)
|
path = appendBit(path, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendBit appends a bit to a path, returning a new slice
|
// appendBit returns a new BitArray with bit appended to path.
|
||||||
func appendBit(path []byte, bit byte) []byte {
|
func appendBit(path BitArray, bit uint8) BitArray {
|
||||||
var p [256]byte
|
var p BitArray
|
||||||
copy(p[:], path)
|
p.AppendBit(&path, bit)
|
||||||
result := p[:len(path)]
|
return p
|
||||||
return append(result, bit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *nodeStore) toDot(ref nodeRef, parent, path string) string {
|
func (s *nodeStore) toDot(ref nodeRef, parent, path string) string {
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,12 @@ func (s *nodeStore) splitStemValuesInsert(existingRef nodeRef, newStem []byte, v
|
||||||
nRef := s.newInternalRef(int(existing.depth))
|
nRef := s.newInternalRef(int(existing.depth))
|
||||||
nNode := s.getInternal(nRef.Index())
|
nNode := s.getInternal(nRef.Index())
|
||||||
existing.depth++
|
existing.depth++
|
||||||
|
// The existing stem's on-disk path is derived from its depth via
|
||||||
|
// extendPathToGroupLeaf. Promoting its depth changes that path, so the
|
||||||
|
// stem must be re-flushed at the new path; otherwise the old blob (at
|
||||||
|
// the prior path) gets overwritten by the new ancestor internal blob
|
||||||
|
// and the stem's data has no on-disk home.
|
||||||
|
existing.dirty = true
|
||||||
|
|
||||||
bitKey := newStem[nNode.depth/8] >> (7 - (nNode.depth % 8)) & 1
|
bitKey := newStem[nNode.depth/8] >> (7 - (nNode.depth % 8)) & 1
|
||||||
if bitKey == bitStem {
|
if bitKey == bitStem {
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,10 @@ func NewBinaryTrie(root common.Hash, db database.NodeDatabase, groupDepth int) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
store := newNodeStore()
|
||||||
|
store.groupDepth = groupDepth
|
||||||
t := &BinaryTrie{
|
t := &BinaryTrie{
|
||||||
store: newNodeStore(),
|
store: store,
|
||||||
reader: reader,
|
reader: reader,
|
||||||
tracer: trie.NewPrevalueTracer(),
|
tracer: trie.NewPrevalueTracer(),
|
||||||
groupDepth: groupDepth,
|
groupDepth: groupDepth,
|
||||||
|
|
@ -319,11 +321,11 @@ func (t *BinaryTrie) Hash() common.Hash {
|
||||||
func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
|
func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
|
||||||
nodeset := trienode.NewNodeSet(common.Hash{})
|
nodeset := trienode.NewNodeSet(common.Hash{})
|
||||||
|
|
||||||
// Pre-size the path buffer: collectNodes reuses it in-place via
|
var rootPath BitArray
|
||||||
// append/truncate; 32 covers typical binary-trie depth without regrowth.
|
t.store.collectNodes(t.store.root, rootPath, func(path BitArray, hash common.Hash, serialized []byte) {
|
||||||
pathBuf := make([]byte, 0, 32)
|
var buf [33]byte
|
||||||
t.store.collectNodes(t.store.root, pathBuf, func(path []byte, hash common.Hash, serialized []byte) {
|
pathBytes := path.PutKeyBytes(buf[:])
|
||||||
nodeset.AddNode(path, trienode.NewNodeWithPrev(hash, serialized, t.tracer.Get(path)))
|
nodeset.AddNode(pathBytes, trienode.NewNodeWithPrev(hash, serialized, t.tracer.Get(pathBytes)))
|
||||||
}, t.groupDepth)
|
}, t.groupDepth)
|
||||||
return t.Hash(), nodeset
|
return t.Hash(), nodeset
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue