github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/snowman/bootstrap/interval/tree.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package interval
     5  
     6  import (
     7  	"github.com/google/btree"
     8  
     9  	"github.com/MetalBlockchain/metalgo/database"
    10  )
    11  
    12  // TODO: Benchmark what degree to use.
    13  const treeDegree = 2
    14  
    15  // Tree implements a set of numbers by tracking intervals. It supports adding
    16  // and removing new values. It also allows checking if a value is included in
    17  // the set.
    18  //
    19  // Tree is more space efficient than a map implementation if the values that it
    20  // contains are continuous. The tree takes O(n) space where n is the number of
    21  // continuous ranges that have been inserted into the tree.
    22  //
    23  // Add, Remove, and Contains all run in O(log n) where n is the number of
    24  // continuous ranges that have been inserted into the tree.
    25  type Tree struct {
    26  	knownHeights *btree.BTreeG[*Interval]
    27  	// If knownHeights contains the full range [0, MaxUint64], then
    28  	// numKnownHeights overflows to 0.
    29  	numKnownHeights uint64
    30  }
    31  
    32  // NewTree creates a new interval tree from the provided database.
    33  //
    34  // It is assumed that persisted intervals are non-overlapping. Providing a
    35  // database with overlapping intervals will result in undefined behavior of the
    36  // structure.
    37  func NewTree(db database.Iteratee) (*Tree, error) {
    38  	intervals, err := GetIntervals(db)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	var (
    44  		knownHeights    = btree.NewG(treeDegree, (*Interval).Less)
    45  		numKnownHeights uint64
    46  	)
    47  	for _, i := range intervals {
    48  		knownHeights.ReplaceOrInsert(i)
    49  		numKnownHeights += i.UpperBound - i.LowerBound + 1
    50  	}
    51  	return &Tree{
    52  		knownHeights:    knownHeights,
    53  		numKnownHeights: numKnownHeights,
    54  	}, nil
    55  }
    56  
    57  func (t *Tree) Add(db database.KeyValueWriterDeleter, height uint64) error {
    58  	var (
    59  		newInterval = &Interval{
    60  			LowerBound: height,
    61  			UpperBound: height,
    62  		}
    63  		upper *Interval
    64  		lower *Interval
    65  	)
    66  	t.knownHeights.AscendGreaterOrEqual(newInterval, func(item *Interval) bool {
    67  		upper = item
    68  		return false
    69  	})
    70  	if upper.Contains(height) {
    71  		// height is already in the tree
    72  		return nil
    73  	}
    74  
    75  	t.knownHeights.DescendLessOrEqual(newInterval, func(item *Interval) bool {
    76  		lower = item
    77  		return false
    78  	})
    79  
    80  	t.numKnownHeights++
    81  
    82  	var (
    83  		adjacentToLowerBound = upper.AdjacentToLowerBound(height)
    84  		adjacentToUpperBound = lower.AdjacentToUpperBound(height)
    85  	)
    86  	switch {
    87  	case adjacentToLowerBound && adjacentToUpperBound:
    88  		// the upper and lower ranges should be merged
    89  		if err := DeleteInterval(db, lower.UpperBound); err != nil {
    90  			return err
    91  		}
    92  		upper.LowerBound = lower.LowerBound
    93  		t.knownHeights.Delete(lower)
    94  		return PutInterval(db, upper.UpperBound, lower.LowerBound)
    95  	case adjacentToLowerBound:
    96  		// the upper range should be extended by one on the lower side
    97  		upper.LowerBound = height
    98  		return PutInterval(db, upper.UpperBound, height)
    99  	case adjacentToUpperBound:
   100  		// the lower range should be extended by one on the upper side
   101  		if err := DeleteInterval(db, lower.UpperBound); err != nil {
   102  			return err
   103  		}
   104  		lower.UpperBound = height
   105  		return PutInterval(db, height, lower.LowerBound)
   106  	default:
   107  		t.knownHeights.ReplaceOrInsert(newInterval)
   108  		return PutInterval(db, height, height)
   109  	}
   110  }
   111  
   112  func (t *Tree) Remove(db database.KeyValueWriterDeleter, height uint64) error {
   113  	var (
   114  		newInterval = &Interval{
   115  			LowerBound: height,
   116  			UpperBound: height,
   117  		}
   118  		higher *Interval
   119  	)
   120  	t.knownHeights.AscendGreaterOrEqual(newInterval, func(item *Interval) bool {
   121  		higher = item
   122  		return false
   123  	})
   124  	if !higher.Contains(height) {
   125  		// height isn't in the tree
   126  		return nil
   127  	}
   128  
   129  	t.numKnownHeights--
   130  
   131  	switch {
   132  	case higher.LowerBound == higher.UpperBound:
   133  		t.knownHeights.Delete(higher)
   134  		return DeleteInterval(db, higher.UpperBound)
   135  	case higher.LowerBound == height:
   136  		higher.LowerBound++
   137  		return PutInterval(db, higher.UpperBound, higher.LowerBound)
   138  	case higher.UpperBound == height:
   139  		if err := DeleteInterval(db, higher.UpperBound); err != nil {
   140  			return err
   141  		}
   142  		higher.UpperBound--
   143  		return PutInterval(db, higher.UpperBound, higher.LowerBound)
   144  	default:
   145  		newInterval.LowerBound = higher.LowerBound
   146  		newInterval.UpperBound = height - 1
   147  		t.knownHeights.ReplaceOrInsert(newInterval)
   148  		if err := PutInterval(db, newInterval.UpperBound, newInterval.LowerBound); err != nil {
   149  			return err
   150  		}
   151  
   152  		higher.LowerBound = height + 1
   153  		return PutInterval(db, higher.UpperBound, higher.LowerBound)
   154  	}
   155  }
   156  
   157  func (t *Tree) Contains(height uint64) bool {
   158  	var (
   159  		i = &Interval{
   160  			LowerBound: height,
   161  			UpperBound: height,
   162  		}
   163  		higher *Interval
   164  	)
   165  	t.knownHeights.AscendGreaterOrEqual(i, func(item *Interval) bool {
   166  		higher = item
   167  		return false
   168  	})
   169  	return higher.Contains(height)
   170  }
   171  
   172  func (t *Tree) Flatten() []*Interval {
   173  	intervals := make([]*Interval, 0, t.knownHeights.Len())
   174  	t.knownHeights.Ascend(func(item *Interval) bool {
   175  		intervals = append(intervals, item)
   176  		return true
   177  	})
   178  	return intervals
   179  }
   180  
   181  // Len returns the number of heights in the tree; not the number of intervals.
   182  //
   183  // Because Len returns a uint64 and is describing the number of values in the
   184  // range of uint64s, it will return 0 if the tree contains the full interval
   185  // [0, MaxUint64].
   186  func (t *Tree) Len() uint64 {
   187  	return t.numKnownHeights
   188  }