fix leaf reconstruction

This commit is contained in:
Guillaume Ballet 2026-02-02 13:55:14 +01:00
parent 68621ae05a
commit 9606dbcb19

View file

@ -67,31 +67,109 @@ func archiveRecordsToNode(records []*archive.Record) (node, error) {
return nil, archive.EmptyArchiveRecord
}
if len(records) == 1 {
return decodeNodeUnsafe(nil, records[0].Value)
return buildLeafFromRecord(records[0])
}
var (
newnode fullNode
curnode *fullNode
)
for _, record := range records {
curnode = &newnode
resolved, err := decodeNodeUnsafe(nil, record.Value)
var newnode fullNode
for i, record := range records {
if err := validateRecordPath(record.Path); err != nil {
return nil, err
}
// we are not in the case of a single leaf node, so each
// path should be at least 2 nibbles (terminator included)
if len(record.Path) < 2 || !hasTerm(record.Path) {
return nil, fmt.Errorf("invalid record path for non-leaf node #%d: %v", i, record.Path)
}
key, err := normalizeRecordKey(record.Path)
if err != nil {
return nil, err
}
// It's not needed to resurrect all nodes, nodes
// not along the path of what has been asked can
// be updated as expired. This is for v2.
for i, b := range record.Path {
if curnode.Children[b] == nil {
if i < len(record.Path)-1 {
curnode.Children[b] = &fullNode{}
} else {
curnode.Children[b] = resolved
}
}
child, err := insertTrieNode(newnode.Children[key[0]], key[1:], valueNode(record.Value))
if err != nil {
return nil, err
}
newnode.Children[key[0]] = child
}
return &newnode, nil
}
func validateRecordPath(path []byte) error {
for i, b := range path {
if b > 16 {
return fmt.Errorf("invalid nibble in record path: %d", b)
}
if b == 16 && i != len(path)-1 {
return fmt.Errorf("terminator nibble in middle of record path")
}
}
return nil
}
func buildLeafFromRecord(record *archive.Record) (node, error) {
key, err := normalizeRecordKey(record.Path)
if err != nil {
return nil, err
}
return &shortNode{Key: key, Val: valueNode(record.Value)}, nil
}
// normalizeRecordKey ensures the record path is a hex-nibble key suitable for
// leaf insertion by guaranteeing a single terminator nibble and preserving any
// already-terminated path. Empty paths are normalized to a sole terminator.
func normalizeRecordKey(path []byte) ([]byte, error) {
if len(path) == 0 {
return []byte{16}, nil
}
if hasTerm(path) {
return path, nil
}
key := append([]byte{}, path...)
key = append(key, 16)
return key, nil
}
func insertTrieNode(n node, key []byte, value node) (node, error) {
if len(key) == 0 {
return value, nil
}
switch n := n.(type) {
case *shortNode:
matchlen := prefixLen(key, n.Key)
if matchlen == len(n.Key) {
nn, err := insertTrieNode(n.Val, key[matchlen:], value)
if err != nil {
return nil, err
}
return &shortNode{Key: n.Key, Val: nn}, nil
}
branch := &fullNode{}
var err error
branch.Children[n.Key[matchlen]], err = insertTrieNode(nil, n.Key[matchlen+1:], n.Val)
if err != nil {
return nil, err
}
branch.Children[key[matchlen]], err = insertTrieNode(nil, key[matchlen+1:], value)
if err != nil {
return nil, err
}
if matchlen == 0 {
return branch, nil
}
return &shortNode{Key: key[:matchlen], Val: branch}, nil
case *fullNode:
child, err := insertTrieNode(n.Children[key[0]], key[1:], value)
if err != nil {
return nil, err
}
n.Children[key[0]] = child
return n, nil
case nil:
return &shortNode{Key: key, Val: value}, nil
default:
return nil, fmt.Errorf("invalid node type in trie insert: %T", n)
}
}