Files
zeekayandHanzo Dev b1cc108c9f gofmt
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 01:51:51 -07:00

791 lines
25 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package merkledb
import (
"bytes"
"context"
"errors"
"fmt"
"math"
"github.com/luxfi/container/maybe"
"github.com/luxfi/database"
"github.com/luxfi/database/memdb"
"github.com/luxfi/ids"
syncpb "github.com/luxfi/node/proto/sync"
"github.com/luxfi/node/trace"
)
const verificationCacheSize = math.MaxUint16
var (
ErrInvalidProof = errors.New("proof obtained an invalid root ID")
ErrInvalidMaxLength = errors.New("expected max length to be > 0")
ErrNonIncreasingValues = errors.New("keys sent are not in increasing order")
ErrStateFromOutsideOfRange = errors.New("state key falls outside of the start->end range")
ErrNonIncreasingProofNodes = errors.New("each proof node key must be a strict prefix of the next")
ErrExtraProofNodes = errors.New("extra proof nodes in path")
ErrDataInMissingRootProof = errors.New("there should be no state or deleted keys in a change proof that had a missing root")
ErrEmptyProof = errors.New("proof is empty")
ErrNoMerkleProof = errors.New("empty key response must include merkle proof")
ErrShouldJustBeRoot = errors.New("end proof should only contain root")
ErrNoEndProof = errors.New("no end proof")
ErrProofNodeNotForKey = errors.New("the provided path has a key that is not a prefix of the specified key")
ErrExclusionProofMissingEndNodes = errors.New("missing end nodes from path")
ErrExclusionProofUnexpectedValue = errors.New("exclusion proof's value should be empty")
ErrExclusionProofInvalidNode = errors.New("invalid node for exclusion proof")
ErrProofValueDoesntMatch = errors.New("the provided value does not match the proof node for the provided key's value")
ErrProofKeyPartialByte = errors.New("the provided key has partial byte length")
ErrProofNodeHasUnincludedValue = errors.New("the provided proof has a value for a key within the range that is not present in the provided key/values")
ErrInvalidMaybe = errors.New("maybe is nothing but has value")
ErrNilProofNode = errors.New("proof node is nil")
ErrNilValueOrHash = errors.New("proof node's valueOrHash field is nil")
ErrNilKey = errors.New("key is nil")
ErrInvalidKeyLength = errors.New("key length doesn't match bytes length, check specified branchFactor")
ErrNilRangeProof = errors.New("range proof is nil")
ErrNilChangeProof = errors.New("change proof is nil")
ErrNilMaybeBytes = errors.New("maybe bytes is nil")
ErrNilProof = errors.New("proof is nil")
ErrNilValue = errors.New("value is nil")
ErrUnexpectedEndProof = errors.New("end proof should be empty")
ErrUnexpectedStartProof = errors.New("start proof should be empty")
)
type ProofNode struct {
Key Key
// Nothing if this is an intermediate node.
// The value in this node if its length < [HashLen].
// The hash of the value in this node otherwise.
ValueOrHash maybe.Maybe[[]byte]
Children map[byte]ids.ID
}
// ToProto converts the ProofNode into the protobuf version of a proof node
// Assumes [node.Key.Key.length] <= math.MaxUint64.
func (node *ProofNode) ToProto() *syncpb.ProofNode {
pbNode := &syncpb.ProofNode{
Key: &syncpb.Key{
Length: uint64(node.Key.length),
Value: node.Key.Bytes(),
},
ValueOrHash: &syncpb.MaybeBytes{
Value: node.ValueOrHash.Value(),
IsNothing: node.ValueOrHash.IsNothing(),
},
Children: make(map[uint32][]byte, len(node.Children)),
}
for childIndex, childID := range node.Children {
pbNode.Children[uint32(childIndex)] = childID[:]
}
return pbNode
}
func (node *ProofNode) UnmarshalProto(pbNode *syncpb.ProofNode) error {
switch {
case pbNode == nil:
return ErrNilProofNode
case pbNode.ValueOrHash == nil:
return ErrNilValueOrHash
case pbNode.ValueOrHash.IsNothing && len(pbNode.ValueOrHash.Value) != 0:
return ErrInvalidMaybe
case pbNode.Key == nil:
return ErrNilKey
case len(pbNode.Key.Value) != bytesNeeded(int(pbNode.Key.Length)):
return ErrInvalidKeyLength
}
node.Key = ToKey(pbNode.Key.Value).Take(int(pbNode.Key.Length))
node.Children = make(map[byte]ids.ID, len(pbNode.Children))
for childIndex, childIDBytes := range pbNode.Children {
if childIndex > math.MaxUint8 {
return errChildIndexTooLarge
}
childID, err := ids.ToID(childIDBytes)
if err != nil {
return err
}
node.Children[byte(childIndex)] = childID
}
if !pbNode.ValueOrHash.IsNothing {
node.ValueOrHash = maybe.Some(pbNode.ValueOrHash.Value)
}
return nil
}
// Proof represents an inclusion/exclusion proof of a key.
type Proof struct {
// Nodes in the proof path from root --> target key
// (or node that would be where key is if it doesn't exist).
// Always contains at least the root.
Path []ProofNode
// This is a proof that [key] exists/doesn't exist.
// Must not have any partial bytes.
Key Key
// Nothing if [Key] isn't in the trie.
// Otherwise, the value corresponding to [Key].
Value maybe.Maybe[[]byte]
}
// Verify returns nil if the trie given in [proof] has root [expectedRootID].
// That is, this is a valid proof that [proof.Key] exists/doesn't exist
// in the trie with root [expectedRootID].
func (proof *Proof) Verify(
ctx context.Context,
expectedRootID ids.ID,
tokenSize int,
hasher Hasher,
) error {
// Make sure the proof is well-formed.
if len(proof.Path) == 0 {
return ErrEmptyProof
}
if proof.Key.hasPartialByte() {
return ErrProofKeyPartialByte
}
lastNode := proof.Path[len(proof.Path)-1]
inclusionProof := lastNode.Key.Compare(proof.Key) == 0
if inclusionProof && !valueOrHashMatches(hasher, proof.Value, lastNode.ValueOrHash) {
return ErrProofValueDoesntMatch
}
if !inclusionProof && proof.Value.HasValue() {
return ErrExclusionProofUnexpectedValue
}
if err := verifyProofPath(proof.Path, proof.Key, tokenSize); err != nil {
return err
}
// Don't bother locking [view] -- nobody else has a reference to it.
view, err := getStandaloneView(ctx, nil, tokenSize)
if err != nil {
return err
}
// Insert all proof nodes.
// [provenKey] is the key that we are proving exists, or the key
// that is the next key along the node path, proving that [proof.Key] doesn't exist in the trie.
provenKey := maybe.Some(lastNode.Key)
if err = addPathInfo(view, proof.Path, provenKey, provenKey); err != nil {
return err
}
gotRootID, err := view.GetMerkleRoot(ctx)
if err != nil {
return err
}
if expectedRootID != gotRootID {
return fmt.Errorf("%w:[%s], expected:[%s]", ErrInvalidProof, gotRootID, expectedRootID)
}
return nil
}
func (proof *Proof) ToProto() *syncpb.Proof {
value := &syncpb.MaybeBytes{
Value: proof.Value.Value(),
IsNothing: proof.Value.IsNothing(),
}
pbProof := &syncpb.Proof{
Key: proof.Key.Bytes(),
Value: value,
}
pbProof.Proof = make([]*syncpb.ProofNode, len(proof.Path))
for i, node := range proof.Path {
pbProof.Proof[i] = node.ToProto()
}
return pbProof
}
func (proof *Proof) UnmarshalProto(pbProof *syncpb.Proof) error {
switch {
case pbProof == nil:
return ErrNilProof
case pbProof.Value == nil:
return ErrNilValue
case pbProof.Value.IsNothing && len(pbProof.Value.Value) != 0:
return ErrInvalidMaybe
}
proof.Key = ToKey(pbProof.Key)
if !pbProof.Value.IsNothing {
proof.Value = maybe.Some(pbProof.Value.Value)
}
proof.Path = make([]ProofNode, len(pbProof.Proof))
for i, pbNode := range pbProof.Proof {
if err := proof.Path[i].UnmarshalProto(pbNode); err != nil {
return err
}
}
return nil
}
type RangeProof ChangeProof
func (proof *RangeProof) ToProto() *syncpb.RangeProof {
startProof := make([]*syncpb.ProofNode, len(proof.StartProof))
for i, node := range proof.StartProof {
startProof[i] = node.ToProto()
}
endProof := make([]*syncpb.ProofNode, len(proof.EndProof))
for i, node := range proof.EndProof {
endProof[i] = node.ToProto()
}
keyValues := make([]*syncpb.KeyValue, len(proof.KeyChanges))
for i, kv := range proof.KeyChanges {
keyValues[i] = &syncpb.KeyValue{
Key: kv.Key,
Value: kv.Value.Value(),
}
}
return &syncpb.RangeProof{
StartProof: startProof,
EndProof: endProof,
KeyValues: keyValues,
}
}
func (proof *RangeProof) UnmarshalProto(pbProof *syncpb.RangeProof) error {
if pbProof == nil {
return ErrNilRangeProof
}
proof.StartProof = make([]ProofNode, len(pbProof.StartProof))
for i, protoNode := range pbProof.StartProof {
if err := proof.StartProof[i].UnmarshalProto(protoNode); err != nil {
return err
}
}
proof.EndProof = make([]ProofNode, len(pbProof.EndProof))
for i, protoNode := range pbProof.EndProof {
if err := proof.EndProof[i].UnmarshalProto(protoNode); err != nil {
return err
}
}
proof.KeyChanges = make([]KeyChange, len(pbProof.KeyValues))
for i, kv := range pbProof.KeyValues {
proof.KeyChanges[i] = KeyChange{
Key: kv.Key,
Value: maybe.Some(kv.Value),
}
}
return nil
}
// Validate received data from change/range proof requests
// using the requested range.
func validateChangeProof(
startKey maybe.Maybe[Key],
endKey maybe.Maybe[Key],
startProof []ProofNode,
endProof []ProofNode,
keyChanges []KeyChange,
endProofKey maybe.Maybe[Key],
tokenSize int,
) error {
switch {
case startKey.HasValue() && endKey.HasValue() && startKey.Value().Compare(endKey.Value()) > 0:
return ErrStartAfterEnd
case len(keyChanges) == 0 && len(startProof) == 0 && len(endProof) == 0:
return ErrEmptyProof
case endKey.IsNothing() && len(keyChanges) == 0 && len(endProof) != 0:
return ErrUnexpectedEndProof
case startKey.IsNothing() && len(startProof) > 0:
return ErrUnexpectedStartProof
case len(endProof) == 0 && (endKey.HasValue() || len(keyChanges) > 0):
return ErrNoEndProof
}
// Make sure the key-value pairs are sorted and in [start, end].
if err := verifySortedKeyChanges(keyChanges, startKey, endKey); err != nil {
return err
}
// Ensure that the start proof is valid.
// If [startProof] is non-empty, [end] is non-empty (length is checked inside verifyProofPath).
if err := verifyProofPath(startProof, startKey.Value(), tokenSize); err != nil {
return fmt.Errorf("failed to verify start proof path: %w", err)
}
// Ensure that the end proof is valid.
// If [endProof] is non-empty, [end] is non-empty (length is checked inside verifyProofPath).
if err := verifyProofPath(endProof, endProofKey.Value(), tokenSize); err != nil {
return fmt.Errorf("failed to verify end proof path: %w", err)
}
return nil
}
// Verify returns nil iff all the following hold:
// - The invariants of RangeProof hold.
// - [start] <= [end].
// - [proof] proves the key-value pairs in [proof.KeyValues] are in the trie
// whose root is [expectedRootID].
//
// All keys in [proof.KeyValues] are in the range [start, end].
//
// If [start] is Nothing, all keys are considered > [start].
// If [end] is Nothing, all keys are considered < [end].
func (proof *RangeProof) Verify(
ctx context.Context,
start maybe.Maybe[[]byte],
end maybe.Maybe[[]byte],
expectedRootID ids.ID,
tokenSize int,
hasher Hasher,
) error {
db, err := newDatabase(
ctx,
memdb.New(),
Config{
BranchFactor: tokenSizeToBranchFactor[tokenSize],
Hasher: hasher,
Tracer: trace.Noop,
ValueNodeCacheSize: verificationCacheSize,
IntermediateNodeCacheSize: verificationCacheSize,
IntermediateWriteBufferSize: verificationCacheSize,
IntermediateWriteBatchSize: verificationCacheSize,
},
&mockMetrics{},
)
if err != nil {
return err
}
return db.VerifyChangeProof(ctx, (*ChangeProof)(proof), start, end, expectedRootID)
}
type KeyChange struct {
Key []byte
Value maybe.Maybe[[]byte]
}
// ChangeProof proves that a set of key-value changes occurred
// between two trie roots, where each key-value pair's key is
// between some lower and upper bound (inclusive).
type ChangeProof struct {
// Invariant: At least one of [StartProof], [EndProof], [KeyChanges] is non-empty.
// An inclusion/exclusion proof for the lower range bound.
//
// If no lower range bound was given, this is empty.
//
// Note that this may not be an entire proof -- nodes are omitted if
// they are also in [EndProof].
StartProof []ProofNode
// If [KeyChanges] is non-empty, this is an inclusion proof of the largest key
// in [KeyChanges].
//
// If [KeyChanges] is empty and an upper range bound was given,
// this is an exclusion proof of the upper range bound.
//
// If [KeyChanges] is empty and no upper range bound was given,
// this is empty.
EndProof []ProofNode
// A subset of key-values that were added, removed, or had their values
// modified between the requested start root (exclusive) and the requested
// end root (inclusive).
// Each key is in the requested range (inclusive).
// The first key-value is the first key-value at/after the range start.
// The key-value pairs are consecutive. That is, if keys k1 and k2 are
// in [KeyChanges] then there is no k3 that was modified between the start and
// end roots such that k1 < k3 < k2.
// This is a subset of the requested key-value range, rather than the entire
// range, because otherwise the proof may be too large.
// Must not have any partial bytes.
// Sorted by increasing key and with no duplicate keys.
//
// Example: Suppose that between the start root and the end root, the following
// key-value pairs were added, removed, or modified:
//
// [kv1, kv2, kv3, kv4, kv5]
// where start <= kv1 < ... < kv5 <= end.
//
// The following are possible values of [KeyChanges]:
//
// []
// [kv1]
// [kv1, kv2]
// [kv1, kv2, kv3]
// [kv1, kv2, kv3, kv4]
// [kv1, kv2, kv3, kv4, kv5]
//
// The following values of [KeyChanges] are always invalid, for example:
//
// [kv2] (Doesn't include kv1, the first key-value at/after the range start)
// [kv1, kv3] (Doesn't include kv2, the key-value between kv1 and kv3)
// [kv1, kv3, kv2] (Not sorted by increasing key)
// [kv1, kv1] (Duplicate key-value pairs)
// [kv0, kv1] (For some kv1 < start)
// [kv1, kv2, kv3, kv4, kv5, kv6] (For some kv6 > end)
KeyChanges []KeyChange
}
func (proof *ChangeProof) ToProto() *syncpb.ChangeProof {
startProof := make([]*syncpb.ProofNode, len(proof.StartProof))
for i, node := range proof.StartProof {
startProof[i] = node.ToProto()
}
endProof := make([]*syncpb.ProofNode, len(proof.EndProof))
for i, node := range proof.EndProof {
endProof[i] = node.ToProto()
}
keyChanges := make([]*syncpb.KeyChange, len(proof.KeyChanges))
for i, kv := range proof.KeyChanges {
keyChanges[i] = &syncpb.KeyChange{
Key: kv.Key,
Value: &syncpb.MaybeBytes{
Value: kv.Value.Value(),
IsNothing: kv.Value.IsNothing(),
},
}
}
return &syncpb.ChangeProof{
StartProof: startProof,
EndProof: endProof,
KeyChanges: keyChanges,
}
}
func (proof *ChangeProof) UnmarshalProto(pbProof *syncpb.ChangeProof) error {
if pbProof == nil {
return ErrNilChangeProof
}
proof.StartProof = make([]ProofNode, len(pbProof.StartProof))
for i, protoNode := range pbProof.StartProof {
if err := proof.StartProof[i].UnmarshalProto(protoNode); err != nil {
return err
}
}
proof.EndProof = make([]ProofNode, len(pbProof.EndProof))
for i, protoNode := range pbProof.EndProof {
if err := proof.EndProof[i].UnmarshalProto(protoNode); err != nil {
return err
}
}
proof.KeyChanges = make([]KeyChange, len(pbProof.KeyChanges))
for i, kv := range pbProof.KeyChanges {
if kv.Value == nil {
return ErrNilMaybeBytes
}
if kv.Value.IsNothing && len(kv.Value.Value) != 0 {
return ErrInvalidMaybe
}
value := maybe.Nothing[[]byte]()
if !kv.Value.IsNothing {
value = maybe.Some(kv.Value.Value)
}
proof.KeyChanges[i] = KeyChange{
Key: kv.Key,
Value: value,
}
}
return nil
}
// Verifies that the given [proofNodes]:
// - if the node's key is within the key range, that has a value that matches the value passed in the change list or in the db.
func verifyChangeProofKeyValues(ctx context.Context, db *merkleDB, keyChanges []KeyChange, proofNodes []ProofNode, start maybe.Maybe[Key], end maybe.Maybe[Key], hasher Hasher) error {
keyChangesMap := map[Key]maybe.Maybe[[]byte]{}
for _, kc := range keyChanges {
keyChangesMap[ToKey(kc.Key)] = kc.Value
}
valueGetter := func(ctx context.Context, k Key) (maybe.Maybe[[]byte], error) {
if kc, ok := keyChangesMap[k]; ok {
return kc, nil
}
// This value isn't in the list of key-value pairs we got.
dbValue, err := db.GetValue(ctx, k.Bytes())
if err != nil {
if errors.Is(err, database.ErrNotFound) {
return maybe.Nothing[[]byte](), nil
}
return maybe.Nothing[[]byte](), err
}
return maybe.Some(dbValue), nil
}
for _, proofNode := range proofNodes {
if proofNode.Key.hasPartialByte() {
continue
}
if (start.IsNothing() || !proofNode.Key.Less(start.Value())) && (end.IsNothing() || !proofNode.Key.Greater(end.Value())) {
value, err := valueGetter(ctx, proofNode.Key)
if err != nil {
return fmt.Errorf("could not get value: %w", err)
}
if value.IsNothing() && proofNode.ValueOrHash.HasValue() {
return ErrProofNodeHasUnincludedValue
}
if value.HasValue() && !valueOrHashMatches(hasher, value, proofNode.ValueOrHash) {
return ErrProofValueDoesntMatch
}
}
}
return nil
}
func (proof *ChangeProof) Empty() bool {
return len(proof.KeyChanges) == 0 &&
len(proof.StartProof) == 0 && len(proof.EndProof) == 0
}
// Returns nil iff both hold:
// 1. [keyChanges] is sorted by key in increasing order.
// 2. All keys in [keyChanges] are in the range [start, end].
// If [start] is nil, there is no lower bound on acceptable keys.
// If [end] is nothing, there is no upper bound on acceptable keys.
// If [keyChanges] is empty, returns nil.
func verifySortedKeyChanges(keyChanges []KeyChange, start maybe.Maybe[Key], end maybe.Maybe[Key]) error {
hasLowerBound := start.HasValue()
hasUpperBound := end.HasValue()
for i := 0; i < len(keyChanges); i++ {
if i < len(keyChanges)-1 && bytes.Compare(keyChanges[i].Key, keyChanges[i+1].Key) >= 0 {
return ErrNonIncreasingValues
}
if (hasLowerBound && bytes.Compare(keyChanges[i].Key, start.Value().Bytes()) < 0) ||
(hasUpperBound && bytes.Compare(keyChanges[i].Key, end.Value().Bytes()) > 0) {
return ErrStateFromOutsideOfRange
}
}
return nil
}
// If the last element in [proof] is [key], this is an inclusion proof.
// Otherwise, this is an exclusion proof and [key] must not be in [proof].
//
// Returns nil iff all the following hold:
//
// - Any node with a partial byte length, should not have a value associated with it
// since all keys with values are written in complete bytes([]byte).
//
// - Each key in [proof] is a strict prefix of the following key.
//
// - Each key in [proof] is a strict prefix of [key], except possibly the last.
//
// - If this is an inclusionProof, the last key in [proof] is the [key].
//
// - If this is an exclusionProof:
// -> the last key in [proof] is the replacement child and is at the corresponding index of the parent's children.
// -> the last key in [proof] is the possible parent and it doesn't have a child at the corresponding index.
func verifyProofPath(proof []ProofNode, key Key, tokenSize int) error {
if len(proof) == 0 {
return nil
}
// loop over all but the last node since it will not have the prefix in exclusion proofs
for i, proofNode := range proof[:len(proof)-1] {
nodeKey := proofNode.Key
// Because the interface only supports []byte keys,
// a key with a partial byte may not store a value
if nodeKey.hasPartialByte() && proofNode.ValueOrHash.HasValue() {
return ErrPartialByteLengthWithValue
}
// each node's key should be a prefix of [key]
if !key.HasStrictPrefix(nodeKey) {
return ErrProofNodeNotForKey
}
// each node's key must be a prefix of the next node's key
nextKey := proof[i+1].Key
if !nextKey.HasStrictPrefix(nodeKey) {
return ErrNonIncreasingProofNodes
}
}
lastNode := proof[len(proof)-1]
if lastNode.Key.hasPartialByte() && !lastNode.ValueOrHash.IsNothing() {
return ErrPartialByteLengthWithValue
}
if lastNode.Key.Compare(key) != 0 {
// exclusionProof
if key.HasPrefix(lastNode.Key) {
// [lastNode] is an ancestor of the node
nextIndex := key.Token(lastNode.Key.length, tokenSize)
if _, ok := lastNode.Children[nextIndex]; ok {
// [lastNode] shouldn't contain any other child at the specific index
return ErrExclusionProofMissingEndNodes
}
} else if len(proof) > 1 {
// For [lastNode] to be the replacement child, it should be at the same index as [key] would be
// inside the parent.
// So, we need to check that the first [number of bits of the parent] + [tokenSize] bits of both,
// [lastNode] and [key] are the same. Otherwise, it means the replacement child is at the wrong index.
lastNodeParent := proof[len(proof)-2]
parentKeyLen := lastNodeParent.Key.Length()
bitsToCheck := parentKeyLen + tokenSize
if !key.HasPrefix(lastNode.Key.Take(bitsToCheck)) {
// [lastNode] at wrong index inside parent
return ErrExclusionProofInvalidNode
}
}
}
return nil
}
// Returns true if [value] and [valueDigest] match.
// [valueOrHash] should be the [ValueOrHash] field of a [ProofNode].
func valueOrHashMatches(
hasher Hasher,
value maybe.Maybe[[]byte],
valueOrHash maybe.Maybe[[]byte],
) bool {
var (
valueIsNothing = value.IsNothing()
digestIsNothing = valueOrHash.IsNothing()
)
switch {
case valueIsNothing != digestIsNothing:
// One is nothing and the other isn't -- no match.
return false
case valueIsNothing:
// Both are nothing -- match.
return true
case len(value.Value()) < HashLength:
return bytes.Equal(value.Value(), valueOrHash.Value())
default:
valueHash := hasher.HashValue(value.Value())
return bytes.Equal(valueHash[:], valueOrHash.Value())
}
}
// Adds each key/value pair in [proofPath] to [t].
// For each proof node, adds the children that are
// < [insertChildrenLessThan] or > [insertChildrenGreaterThan].
// If [insertChildrenLessThan] is Nothing, no children are < [insertChildrenLessThan].
// If [insertChildrenGreaterThan] is Nothing, no children are > [insertChildrenGreaterThan].
// Assumes [v.lock] is held.
func addPathInfo(
v *view,
proofPath []ProofNode,
insertChildrenLessThan maybe.Maybe[Key],
insertChildrenGreaterThan maybe.Maybe[Key],
) error {
var (
shouldInsertLeftChildren = insertChildrenLessThan.HasValue()
shouldInsertRightChildren = insertChildrenGreaterThan.HasValue()
)
for i := len(proofPath) - 1; i >= 0; i-- {
proofNode := proofPath[i]
key := proofNode.Key
if key.hasPartialByte() && !proofNode.ValueOrHash.IsNothing() {
return ErrPartialByteLengthWithValue
}
// load the node associated with the key or create a new one
// pass nothing because we are going to overwrite the value digest below
n, err := v.insert(key, maybe.Nothing[[]byte]())
if err != nil {
return err
}
// We overwrite the valueDigest to be the hash provided in the proof
// node because we may not know the pre-image of the valueDigest.
n.valueDigest = proofNode.ValueOrHash
if !shouldInsertLeftChildren && !shouldInsertRightChildren {
// No children of proof nodes are outside the range.
// No need to add any children to [n].
continue
}
// Add [proofNode]'s children which are outside the range
// [insertChildrenLessThan, insertChildrenGreaterThan].
// What is inside the range, should be included in provided key-values.
for index, childID := range proofNode.Children {
var compressedKey Key
if existingChild, ok := n.children[index]; ok {
compressedKey = existingChild.compressedKey
}
childKey := key.Extend(ToToken(index, v.tokenSize), compressedKey)
if (shouldInsertLeftChildren && childKey.Less(insertChildrenLessThan.Value())) ||
(shouldInsertRightChildren && childKey.Greater(insertChildrenGreaterThan.Value())) {
// We don't set the [hasValue] field of the child but that's OK.
// We only need the compressed key and ID to be correct so that the
// calculated hash is correct.
n.setChildEntry(
index,
&child{
id: childID,
compressedKey: compressedKey,
})
}
}
}
return nil
}
// getStandaloneView returns a new view that has nothing in it besides the changes due to [ops]
func getStandaloneView(ctx context.Context, ops []database.BatchOp, size int) (*view, error) {
db, err := newDatabase(
ctx,
memdb.New(),
Config{
BranchFactor: tokenSizeToBranchFactor[size],
Tracer: trace.Noop,
ValueNodeCacheSize: verificationCacheSize,
IntermediateNodeCacheSize: verificationCacheSize,
IntermediateWriteBufferSize: verificationCacheSize,
IntermediateWriteBatchSize: verificationCacheSize,
},
&mockMetrics{},
)
if err != nil {
return nil, err
}
return newView(db, db, ViewChanges{BatchOps: ops, ConsumeBytes: true})
}