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 }