erigon-pulse/commitment/bin_patricia_trie.go
ledgerwatch 900c03b1ba
Fixes for bin_patricia_hashed (#441)
* Debug bin commitment

* Fix print

* Fix print

* Print

* Print

* Print

* Print

* Print

* Print

* Print

* Print

* Fix binString

* Fix binString

* Fix binString

* Print

* Print

* Print

* Print

* Print

* Print

* Print

* Print

* Print

* Print

* Print

* Print

* Print

* No print

* No print

* Clean up

* Fix lint

* Fixing test

* Skip some tests

Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local>
Co-authored-by: Alex Sharp <alexsharp@Alexs-MacBook-Pro.local>
2022-05-14 20:45:37 +01:00

915 lines
22 KiB
Go

package commitment
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"strings"
"github.com/ledgerwatch/erigon-lib/common/length"
"golang.org/x/crypto/sha3"
)
type BinPatriciaTrie struct {
root *RootNode
trace bool
stat stat
keccak keccakState
}
type stat struct {
hashesTotal uint64
nodesTotal uint64
}
func NewBinaryPatriciaTrie() *BinPatriciaTrie {
return &BinPatriciaTrie{
keccak: sha3.NewLegacyKeccak256().(keccakState),
}
}
func (t *BinPatriciaTrie) Update(key, value []byte) {
keyPath := newBitstring(key)
if t.root == nil {
t.root = &RootNode{
Node: &Node{
Key: key,
Value: value,
},
CommonPrefix: keyPath,
}
t.stat.nodesTotal++
return
}
edge, keyPathRest, splitAt, latest := t.root.Walk(keyPath, func(_ *Node) {})
if len(edge) == 0 && len(keyPathRest) == 0 {
latest.Value = value
return
}
pathToLatest := keyPath[:len(keyPath)-len(keyPathRest)]
t.stat.nodesTotal++
newLeaf := &Node{P: latest, Key: key, Value: value}
latest.splitEdge(pathToLatest, edge[:splitAt], edge[splitAt:], keyPathRest, newLeaf, t.hash)
if latest.P == nil {
t.root.CommonPrefix = edge[:splitAt]
}
}
// Key describes path in trie to value. When UpdateHashed is used,
// hashed key describes path to the leaf node and plainKey is stored in the leaf node Key field.
func (t *BinPatriciaTrie) UpdateHashed(plainKey, hashedKey, value []byte, isStorage bool) (updates map[string][]byte) {
keyPath := newBitstring(hashedKey)
updates = make(map[string][]byte)
if t.root == nil {
t.root = &RootNode{
Node: &Node{
Key: plainKey,
Value: value,
isStorage: isStorage,
},
CommonPrefix: keyPath,
}
t.stat.nodesTotal++
t.hash(t.root.Node, keyPath, 0)
touchMap := uint16(1 << keyPath[len(keyPath)-1])
updates[keyPath.String()] = encodeNodeUpdate(t.root.Node, touchMap, touchMap, nil)
return updates
}
touchedNodes := make([]*Node, 0)
edge, keyPathRest, splitAt, latest := t.root.Walk(keyPath, func(fn *Node) { fn.hash = fn.hash[:0]; touchedNodes = append(touchedNodes, fn) })
pathToLatest := keyPath[:len(keyPath)-len(keyPathRest)]
var touchMap uint16
if len(edge) == 0 && len(keyPathRest) == 0 { // we found the leaf
latest.Value = value
t.hash(latest, bitstring{}, 0)
touchMap = 1 << edge[len(edge)-1]
updates[pathToLatest.String()] = encodeNodeUpdate(latest, touchMap, touchMap, nil)
return updates
}
// split existing edge and insert new leaf
t.stat.nodesTotal++
newLeaf := &Node{P: latest, Key: plainKey, Value: value, isStorage: isStorage}
updates = latest.splitEdge(pathToLatest, edge[:splitAt], edge[splitAt:], keyPathRest, newLeaf, t.hash)
if latest.P == nil {
t.root.CommonPrefix = edge[:splitAt]
}
return updates
}
// Get returns value stored by provided key.
func (t *BinPatriciaTrie) Get(key []byte) ([]byte, bool) {
keyPath := newBitstring(key)
if t.root == nil {
return nil, false
}
edge, keyPathRest, _, latest := t.root.Walk(keyPath, func(_ *Node) {})
if len(edge) == 0 && len(keyPathRest) == 0 {
if latest.Value == nil {
switch {
case len(latest.LPrefix) == 0 && latest.L != nil:
return latest.L.Value, true
case len(latest.RPrefix) == 0 && latest.R != nil:
return latest.R.Value, true
}
}
return latest.Value, true
}
return nil, false
}
// Get returns value stored by provided key.
func (t *BinPatriciaTrie) getNode(key []byte) *Node {
keyPath := newBitstring(key)
if t.root == nil {
return nil
}
edge, keyPathRest, _, latest := t.root.Walk(keyPath, func(_ *Node) {})
if len(edge) == 0 && len(keyPathRest) == 0 {
return latest
}
return nil
}
func (t *BinPatriciaTrie) RootHash() ([]byte, error) {
if t.root == nil {
return EmptyRootHash, nil
}
return t.hash(t.root.Node, t.root.CommonPrefix, 0), nil
}
func (t *BinPatriciaTrie) ProcessUpdates(plainKeys, hashedKeys [][]byte, updates []Update) (branchNodeUpdates map[string][]byte, err error) {
branchNodeUpdates = make(map[string][]byte)
for i, update := range updates {
account := new(Account)
node := t.getNode(hashedKeys[i]) // check if key exist
if node != nil && !node.isStorage {
account.decode(node.Value)
}
// apply supported updates
if update.Flags == DELETE_UPDATE {
//continue
if node != nil {
if node.P != nil {
meltPrefix := node.P.deleteChild(node)
if node.P.P == nil {
t.root.CommonPrefix = append(t.root.CommonPrefix, meltPrefix...)
}
} else { // remove root
t.root = nil
}
t.stat.nodesTotal--
branchNodeUpdates[hexToBin(hashedKeys[i]).String()] = []byte{}
}
continue
}
if update.Flags&BALANCE_UPDATE != 0 {
account.Balance.Set(&update.Balance)
}
if update.Flags&NONCE_UPDATE != 0 {
account.Nonce = update.Nonce
}
if update.Flags&CODE_UPDATE != 0 {
if account.CodeHash == nil {
account.CodeHash = make([]byte, len(update.CodeHashOrStorage))
}
copy(account.CodeHash, update.CodeHashOrStorage[:])
}
aux := make([]byte, 0)
isStorage := false
if update.Flags&STORAGE_UPDATE != 0 {
isStorage = true
aux = update.CodeHashOrStorage[:update.ValLength]
}
// aux is not empty only when storage update is there
if len(aux) == 0 {
aux = account.encode(aux)
}
ukey := t.UpdateHashed(plainKeys[i], hashedKeys[i], aux, isStorage)
for pref, val := range ukey {
branchNodeUpdates[pref] = val
if val != nil && t.trace {
fmt.Printf("%q => %s\n", pref, branchToString2(val))
}
}
for pref, upd := range t.rootHashWithUpdates() {
v, ex := branchNodeUpdates[pref]
if ex {
upd = append(v[:4], upd[4:]...)
}
branchNodeUpdates[pref] = upd
}
}
return branchNodeUpdates, nil
}
func DecodeNodeFromUpdate(buf []byte) (touch, after uint16, node Node, err error) {
if len(buf) < 5 {
return
}
touch = binary.BigEndian.Uint16(buf[:2])
after = binary.BigEndian.Uint16(buf[2:4])
bits, pos := PartFlags(buf[4]), 5
if bits&ACCOUNT_PLAIN_PART != 0 {
n, aux, err := decodeSizedBuffer(buf[pos:])
if err != nil {
return touch, after, Node{}, fmt.Errorf("decode account plain key: %w", err)
}
pos += n
node.Key = aux
}
if bits&STORAGE_PLAIN_PART != 0 {
n, aux, err := decodeSizedBuffer(buf[pos:])
if err != nil {
return touch, after, Node{}, fmt.Errorf("decode storage plain key: %w", err)
}
pos += n
node.Key = aux
node.isStorage = true
}
if bits&HASH_PART != 0 {
n, aux, err := decodeSizedBuffer(buf[pos:])
if err != nil {
return touch, after, Node{}, fmt.Errorf("decode node hash: %w", err)
}
pos += n
_ = pos
node.hash = aux
}
return
}
func encodeNodeUpdate(node *Node, touched, after uint16, branchData []byte) []byte {
var numBuf [binary.MaxVarintLen64]byte
binary.BigEndian.PutUint16(numBuf[0:], touched)
binary.BigEndian.PutUint16(numBuf[2:], after)
if branchData == nil {
branchData = make([]byte, 4, 32)
}
copy(branchData[:4], numBuf[:])
var fieldBits PartFlags
if node.Value != nil {
fieldBits = ACCOUNT_PLAIN_PART
if node.isStorage {
fieldBits = STORAGE_PLAIN_PART
}
}
if len(node.hash) == length.Hash {
fieldBits |= HASH_PART
}
branchData = append(branchData, byte(fieldBits))
if fieldBits&(ACCOUNT_PLAIN_PART|STORAGE_PLAIN_PART) != 0 {
n := binary.PutUvarint(numBuf[:], uint64(len(node.Key)))
branchData = append(branchData, append(numBuf[:n], node.Key...)...)
}
if fieldBits&HASH_PART > 0 {
n := binary.PutUvarint(numBuf[:], uint64(len(node.hash)))
branchData = append(branchData, append(numBuf[:n], node.hash...)...)
}
return branchData
}
func (t *BinPatriciaTrie) Reset() {
t.root = nil
fmt.Printf("trie %v\n", t.StatString())
}
func (t *BinPatriciaTrie) ResetFns(
branchFn func(prefix []byte) ([]byte, error),
accountFn func(plainKey []byte, cell *Cell) error,
storageFn func(plainKey []byte, cell *Cell) error,
) {
}
func (t *BinPatriciaTrie) Variant() TrieVariant { return VariantBinPatriciaTrie }
func (t *BinPatriciaTrie) SetTrace(b bool) { t.trace = b }
type RootNode struct {
*Node
CommonPrefix bitstring
}
// There are three types of nodes:
// - Leaf (with a value and without branches)
// - Branch (with left and right child)
// - Root - Either leaf or branch. When root is branch, it's Key contains their common prefix as a bitstring.
type Node struct {
L, R, P *Node // left and right child, parent. For root P is nil
LPrefix bitstring // left child prefix, always begins with 0
RPrefix bitstring // right child prefix, always begins with 1
hash []byte // node hash
Key []byte // same as common prefix, useful for debugging, actual key should be reconstructed by path to the node
Value []byte // exists only in LEAF node
isStorage bool // if true, then Value holds storage value for the Key, otherwise it holds encoded account
}
func (n *Node) splitEdge(pathToNode, commonPath, detachedPath, restKeyPath bitstring, newLeaf *Node, hasher func(n *Node, pref bitstring, offt int) []byte) map[string][]byte {
var movedNode *Node
switch {
case n.Value == nil:
movedNode = &Node{ // move existed branch
L: n.L,
R: n.R,
P: n,
LPrefix: n.LPrefix,
RPrefix: n.RPrefix,
hash: n.hash,
}
movedNode.L.P, movedNode.R.P = movedNode, movedNode
default:
movedNode = &Node{ // move existed leaf
P: n,
Key: n.Key,
Value: n.Value,
isStorage: n.isStorage,
hash: n.hash,
}
}
newLeaf.P = n
switch restKeyPath[0] {
case 0:
n.LPrefix, n.L = restKeyPath, newLeaf
n.RPrefix, n.R = detachedPath, movedNode
case 1:
n.LPrefix, n.L = detachedPath, movedNode
n.RPrefix, n.R = restKeyPath, newLeaf
}
// node become extended, reset key and value
n.Key, n.Value, n.hash, n.isStorage = nil, nil, nil, false
hasher(n, commonPath, 0)
nodeTouch := uint16(1 << pathToNode[len(pathToNode)-1])
nodeAfter := uint16(3) // both child has been updated
updates := make(map[string][]byte, 3)
hasher(n, bitstring{}, 0)
updates[pathToNode.String()] = encodeNodeUpdate(n, nodeTouch, nodeAfter, nil)
rtouch := uint16(1 << restKeyPath[0])
updates[append(pathToNode, restKeyPath[0]).String()] = encodeNodeUpdate(newLeaf, rtouch, rtouch, nil)
if n.P == nil {
// commonPath should be set to RootNode.CommonPrefix outside the function
return updates
}
if len(commonPath) > 0 {
switch commonPath[0] {
case 1:
//if n.P != nil {
n.P.RPrefix = commonPath
//}
//n.RPrefix = commonPath
case 0:
//if n.P != nil {
n.P.LPrefix = commonPath
//}
//n.LPrefix = commonPath
}
}
return updates
}
func (n *RootNode) Walk(path bitstring, fn func(cd *Node)) (nodePath, pathRest bitstring, splitAt int, current *Node) {
nodePath = n.CommonPrefix
var bit uint8
var equal bool
for current = n.Node; current != nil; {
fn(current)
splitAt, bit, equal = nodePath.splitPoint(path)
if equal {
return bitstring{}, bitstring{}, 0, current
}
if splitAt < len(nodePath) {
return nodePath, path[splitAt:], splitAt, current
}
if splitAt == 0 || splitAt == len(nodePath) {
path = path[splitAt:]
switch bit {
case 1:
if current.R == nil {
return nodePath, path, splitAt, current
}
nodePath = current.RPrefix
current = current.R
case 0:
if current.L == nil {
return nodePath, path, splitAt, current
}
nodePath = current.LPrefix
current = current.L
}
continue
}
break
}
return nodePath, path, splitAt, current
}
func (n *Node) deleteChild(child *Node) bitstring {
var melt *Node
var meltPrefix bitstring
// remove child data
switch child {
case n.L:
n.L, n.LPrefix = nil, nil
melt = n.R
meltPrefix = n.RPrefix
case n.R:
n.R, n.RPrefix = nil, nil
melt = n.L
meltPrefix = n.LPrefix
default:
panic("could delete only child nodes")
}
melt.P = n.P
// merge parent path to skip this half-branch node
if n.P != nil {
switch {
case n.P.L == n:
n.P.L, n.P.LPrefix = melt, append(n.P.LPrefix, meltPrefix...)
case n.P.R == n:
n.P.R, n.P.RPrefix = melt, append(n.P.RPrefix, meltPrefix...)
default:
panic("failed to merge parent path")
}
} else { // n is root
n.LPrefix, n.RPrefix = melt.LPrefix, melt.RPrefix
n.Key = melt.Key
n.Value = melt.Value
n.L, n.R, n.Value = melt.L, melt.R, melt.Value
n.hash = n.hash[:0]
}
return meltPrefix
}
func (t *BinPatriciaTrie) StatString() string {
s := t.stat
return fmt.Sprintf("hashes_total %d nodes %d", s.hashesTotal, s.nodesTotal)
}
func (t *BinPatriciaTrie) rootHashWithUpdates() map[string][]byte {
//if t.root == nil {
// return EmptyRootHash, nil
//}
//return t.hash(t.root.Node, t.root.CommonPrefix, 0), nil
updates := make(map[string][]byte)
t.hashWithUpdates(t.root.Node, t.root.CommonPrefix, &updates)
return updates
}
func (t *BinPatriciaTrie) hashWithUpdates(n *Node, pref bitstring, updates *map[string][]byte) ([]byte, []byte) {
if len(n.hash) == 32 {
return n.hash, nil
}
t.keccak.Reset()
t.stat.hashesTotal++
var hash []byte
if n.Value == nil {
// This is a branch node, so the rule is
// branch_hash = hash(left_root_hash || right_root_hash)
lkey := bitstring(make([]byte, len(pref)+len(n.LPrefix)))
copy(lkey, pref)
copy(lkey[len(pref):], n.LPrefix)
rkey := bitstring(make([]byte, len(pref)+len(n.RPrefix)))
copy(rkey, pref)
copy(rkey[len(pref):], n.RPrefix)
lh, lupd := t.hashWithUpdates(n.L, lkey, updates)
rh, rupd := t.hashWithUpdates(n.R, rkey, updates)
t.keccak.Write(lh)
t.keccak.Write(rh)
hash = t.keccak.Sum(nil)
if len(lupd) > 0 {
binary.BigEndian.PutUint16(lupd[0:], 1)
(*updates)[lkey.String()] = lupd
}
if len(rupd) > 0 {
binary.BigEndian.PutUint16(rupd[0:], 2)
(*updates)[rkey.String()] = rupd
}
if t.trace {
fmt.Printf("branch %v (%v|%v)\n", hex.EncodeToString(hash), hex.EncodeToString(lh), hex.EncodeToString(rh))
}
t.keccak.Reset()
} else {
// This is a leaf node, so the hashing rule is
// leaf_hash = hash(hash(key) || hash(leaf_value))
t.keccak.Write(n.Key)
kh := t.keccak.Sum(nil)
t.keccak.Reset()
t.keccak.Write(n.Value)
hash = t.keccak.Sum(nil)
t.keccak.Reset()
t.keccak.Write(kh)
t.keccak.Write(hash)
hash = t.keccak.Sum(nil)
t.keccak.Reset()
if t.trace {
fmt.Printf("leaf %v\n", hex.EncodeToString(hash))
}
}
n.hash = hash
upd := encodeNodeUpdate(n, 0, 3, nil)
if n.P == nil {
binary.BigEndian.PutUint16(upd[0:], 3)
(*updates)[pref.String()] = upd
}
return hash, upd
}
func (t *BinPatriciaTrie) hash(n *Node, pref bitstring, off int) []byte {
t.keccak.Reset()
t.stat.hashesTotal++
if len(n.hash) == 32 && n.P != nil {
return n.hash
}
var hash []byte
if n.Value == nil {
// This is a branch node, so the rule is
// branch_hash = hash(left_root_hash || right_root_hash)
lh := t.hash(n.L, n.LPrefix, off+len(pref))
rh := t.hash(n.R, n.RPrefix, off+len(pref))
t.keccak.Write(lh)
t.keccak.Write(rh)
hash = t.keccak.Sum(nil)
if t.trace {
fmt.Printf("branch %v (%v|%v)\n", hex.EncodeToString(hash), hex.EncodeToString(lh), hex.EncodeToString(rh))
}
t.keccak.Reset()
} else {
// This is a leaf node, so the hashing rule is
// leaf_hash = hash(hash(key) || hash(leaf_value))
t.keccak.Write(n.Key)
kh := t.keccak.Sum(nil)
t.keccak.Reset()
t.keccak.Write(n.Value)
hash = t.keccak.Sum(nil)
t.keccak.Reset()
t.keccak.Write(kh)
t.keccak.Write(hash)
hash = t.keccak.Sum(nil)
t.keccak.Reset()
if t.trace {
fmt.Printf("leaf %v\n", hex.EncodeToString(hash))
}
}
//if len(pref) > 1 {
// fpLen := len(pref) + off
// t.keccak.Write([]byte{byte(fpLen), byte(fpLen >> 8)})
// t.keccak.Write(zero30)
// t.keccak.Write(hash)
//
// hash = t.keccak.Sum(nil)
// t.keccak.Reset()
//}
//if t.trace {
// fmt.Printf("hash %v off %d, pref %d\n", hex.EncodeToString(hash), off, len(pref))
//}
n.hash = hash
return hash
}
var Zero30 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
type bitstring []uint8
func hexToBin(hex []byte) bitstring {
bin := make([]byte, 4*len(hex))
for i := range bin {
if hex[i/4]&(1<<(3-i%4)) != 0 {
bin[i] = 1
}
}
return bin
}
func newBitstring(key []byte) bitstring {
bits := make([]byte, 8*len(key))
for i := range bits {
if key[i/8]&(1<<(7-i%7)) != 0 {
bits[i] = 1
}
}
return bits
}
func bitstringWithPadding(key []byte, _ int) bitstring {
bs := newBitstring(key)
if last := key[len(key)-1]; last&0xf0 != 0 {
padding := int(0xf0 ^ last)
bs = bs[:len(bs)-8-padding]
}
// bs = bs[:len(bs)-padding-1]
return bs
}
func (b bitstring) String() string {
var s string
for _, bit := range b {
switch bit {
case 1:
s += "1"
case 0:
s += "0"
default:
panic(fmt.Errorf("invalid bit %d in bitstring", bit))
}
}
return s
}
func (b bitstring) splitPoint(other bitstring) (at int, bit byte, equal bool) {
for ; at < len(b) && at < len(other); at++ {
if b[at] != other[at] {
return at, other[at], false
}
}
switch {
case len(b) == len(other):
return 0, 0, true
case at == len(b): // b ends before other
return at, other[at], false
case at == len(other): // other ends before b
return at, b[at], false
default:
panic("oro")
}
}
// Converts b into slice of bytes.
// if len of b is not a multiple of 8, we add 1 <= padding <= 7 zeros to the latest byte
// and return amount of added zeros
func (b bitstring) reconstructHex() (re []byte, padding int) {
re = make([]byte, len(b)/8)
var offt, i int
for {
bt, ok := b.readByte(offt)
if !ok {
break
}
re[i] = bt
offt += 8
i++
}
if offt >= len(b) {
return re, 0
}
padding = offt + 8 - len(b)
pd := append(b[offt:], bytes.Repeat([]byte{0}, padding)...)
last, ok := pd.readByte(0)
if !ok {
panic(fmt.Errorf("reconstruct failed: padding %d padded size %d", padding, len(pd)))
}
pad := byte(padding | 0xf0)
return append(re, last, pad), padding
}
func (b bitstring) readByte(offsetBits int) (byte, bool) {
if len(b) <= offsetBits+7 {
return 0, false
}
return b[offsetBits+7] | b[offsetBits+6]<<1 | b[offsetBits+5]<<2 | b[offsetBits+4]<<3 | b[offsetBits+3]<<4 | b[offsetBits+2]<<5 | b[offsetBits+1]<<6 | b[offsetBits]<<7, true
}
// ExtractPlainKeys parses branchData and extract the plain keys for accounts and storage in the same order
// they appear witjin the branchData
func ExtractBinPlainKeys(branchData []byte) (accountPlainKeys [][]byte, storagePlainKeys [][]byte, err error) {
storagePlainKeys = make([][]byte, 0)
accountPlainKeys = make([][]byte, 0)
touchMap := binary.BigEndian.Uint16(branchData[0:])
afterMap := binary.BigEndian.Uint16(branchData[2:])
pos := 4
for bitset, noop := touchMap&afterMap, 0; bitset != 0; noop++ {
if pos >= len(branchData) {
break
}
bit := bitset & -bitset
fieldBits := PartFlags(branchData[pos])
pos++
if fieldBits&ACCOUNT_PLAIN_PART > 0 {
n, aux, err := decodeSizedBuffer(branchData[pos:])
if err != nil {
return nil, nil, fmt.Errorf("extractBinPlainKeys: [%x] account %w", branchData, err)
}
accountPlainKeys = append(accountPlainKeys, aux)
pos += n
}
if fieldBits&STORAGE_PLAIN_PART > 0 {
n, aux, err := decodeSizedBuffer(branchData[pos:])
if err != nil {
return nil, nil, fmt.Errorf("extractBinPlainKeys: storage %w", err)
}
storagePlainKeys = append(storagePlainKeys, aux)
pos += n
}
if fieldBits&HASH_PART > 0 {
n, _, err := decodeSizedBuffer(branchData[pos:])
if err != nil {
return nil, nil, fmt.Errorf("extractBinPlainKeys: hash %w", err)
}
pos += n
}
bitset ^= bit
}
return
}
func decodeSizedBuffer(buf []byte) (int, []byte, error) {
sz, n := binary.Uvarint(buf)
switch {
case n == 0:
return 0, nil, fmt.Errorf("buffer size too small")
case n < 0:
return 0, nil, fmt.Errorf("value overflow")
default:
}
size := int(sz)
if len(buf) < n+size {
return n, []byte{}, fmt.Errorf("encoded size larger than buffer size")
}
return n + size, buf[n : n+size], nil
}
func ReplaceBinPlainKeys(branchData []byte, accountPlainKeys [][]byte, storagePlainKeys [][]byte, newData []byte) ([]byte, error) {
var numBuf [binary.MaxVarintLen64]byte
touchMap := binary.BigEndian.Uint16(branchData[0:])
afterMap := binary.BigEndian.Uint16(branchData[2:])
pos := 4
if cap(newData) < 4 {
newData = make([]byte, 4)
}
copy(newData, branchData[:4])
var accountI, storageI int
for bitset, noop := touchMap&afterMap, 0; bitset != 0; noop++ {
if pos >= len(branchData) {
break
}
bit := bitset & -bitset
fieldBits := PartFlags(branchData[pos])
newData = append(newData, byte(fieldBits))
pos++
if fieldBits == 0 {
continue
}
if fieldBits&ACCOUNT_PLAIN_PART > 0 {
ptr, _, err := decodeSizedBuffer(branchData[pos:])
if err != nil {
return nil, fmt.Errorf("replaceBinPlainKeys: account %w", err)
}
n := binary.PutUvarint(numBuf[:], uint64(len(accountPlainKeys[accountI])))
newData = append(newData, numBuf[:n]...)
newData = append(newData, accountPlainKeys[accountI]...)
accountI++
pos += ptr
}
if fieldBits&STORAGE_PLAIN_PART > 0 {
ptr, _, err := decodeSizedBuffer(branchData[pos:])
if err != nil {
return nil, fmt.Errorf("replaceBinPlainKeys: storage %w", err)
}
n := binary.PutUvarint(numBuf[:], uint64(len(storagePlainKeys[storageI])))
newData = append(newData, numBuf[:n]...)
newData = append(newData, storagePlainKeys[storageI]...)
storageI++
pos += ptr
}
if fieldBits&HASH_PART > 0 {
n, _, err := decodeSizedBuffer(branchData[pos:])
if err != nil {
return nil, fmt.Errorf("extractBinPlainKeys: hash %w", err)
}
newData = append(newData, branchData[pos:pos+n]...)
pos += n
}
bitset ^= bit
}
return newData, nil
}
func branchToString2(branchData []byte) string {
if len(branchData) == 0 {
return "{ DELETED }"
}
touchMap := binary.BigEndian.Uint16(branchData[0:])
afterMap := binary.BigEndian.Uint16(branchData[2:])
pos := 4
var sb strings.Builder
fmt.Fprintf(&sb, "touchMap %016b, afterMap %016b\n", touchMap, afterMap)
for bitset, noop := touchMap&afterMap, 0; bitset != 0; noop++ {
if pos >= len(branchData) {
break
}
bit := bitset & -bitset
if pos >= len(branchData) {
break
}
fieldBits := PartFlags(branchData[pos])
pos++
sb.WriteString("{")
if fieldBits&ACCOUNT_PLAIN_PART > 0 {
n, pk, err := decodeSizedBuffer(branchData[pos:])
if err != nil {
panic(err)
}
fmt.Fprintf(&sb, "accountPlainKey=[%x]", pk)
pos += n
}
if fieldBits&STORAGE_PLAIN_PART > 0 {
n, pk, err := decodeSizedBuffer(branchData[pos:])
if err != nil {
panic(err)
}
fmt.Fprintf(&sb, "storagePlainKey=[%x]", pk)
pos += n
}
if fieldBits&HASH_PART > 0 {
n, hash, err := decodeSizedBuffer(branchData[pos:])
if err != nil {
panic(err)
}
fmt.Fprintf(&sb, " hash=[%x]", hash)
pos += n
}
sb.WriteString(" }\n")
bitset ^= bit
}
return sb.String()
}