diff --git a/trie/expired_node.go b/trie/expired_node.go index 18957ccfcc..27fef5dc9c 100644 --- a/trie/expired_node.go +++ b/trie/expired_node.go @@ -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) + } +}