github.com/ethersphere/bee/v2@v2.2.0/pkg/feeds/epochs/finder.go (about) 1 // Copyright 2021 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package epochs 6 7 import ( 8 "context" 9 "errors" 10 11 "github.com/ethersphere/bee/v2/pkg/feeds" 12 storage "github.com/ethersphere/bee/v2/pkg/storage" 13 "github.com/ethersphere/bee/v2/pkg/swarm" 14 ) 15 16 var _ feeds.Lookup = (*finder)(nil) 17 var _ feeds.Lookup = (*asyncFinder)(nil) 18 19 // finder encapsulates a chunk store getter and a feed and provides 20 // non-concurrent lookup methods 21 type finder struct { 22 getter *feeds.Getter 23 } 24 25 // NewFinder constructs an AsyncFinder 26 func NewFinder(getter storage.Getter, feed *feeds.Feed) feeds.Lookup { 27 return &finder{feeds.NewGetter(getter, feed)} 28 } 29 30 // At looks up the version valid at time `at` 31 // after is a unix time hint of the latest known update 32 func (f *finder) At(ctx context.Context, at int64, after uint64) (swarm.Chunk, feeds.Index, feeds.Index, error) { 33 e, ch, err := f.common(ctx, at, after) 34 if err != nil { 35 return nil, nil, nil, err 36 } 37 ch, err = f.at(ctx, uint64(at), e, ch) 38 return ch, nil, nil, err 39 } 40 41 // common returns the lowest common ancestor for which a feed update chunk is found in the chunk store 42 func (f *finder) common(ctx context.Context, at int64, after uint64) (*epoch, swarm.Chunk, error) { 43 for e := lca(uint64(at), after); ; e = e.parent() { 44 ch, err := f.getter.Get(ctx, e) 45 if err != nil { 46 if errors.Is(err, storage.ErrNotFound) { 47 if e.level == maxLevel { 48 return e, nil, nil 49 } 50 continue 51 } 52 return e, nil, err 53 } 54 ts, err := feeds.UpdatedAt(ch) 55 if err != nil { 56 return e, nil, err 57 } 58 if ts <= uint64(at) { 59 return e, ch, nil 60 } 61 } 62 } 63 64 // at is a non-concurrent recursive Finder function to find the version update chunk at time `at` 65 func (f *finder) at(ctx context.Context, at uint64, e *epoch, ch swarm.Chunk) (swarm.Chunk, error) { 66 uch, err := f.getter.Get(ctx, e) 67 if err != nil { 68 // error retrieving 69 if !errors.Is(err, storage.ErrNotFound) { 70 return nil, err 71 } 72 // epoch not found on branch 73 if e.isLeft() { // no lower resolution 74 return ch, nil 75 } 76 // traverse earlier branch 77 return f.at(ctx, e.start-1, e.left(), ch) 78 } 79 // epoch found 80 // check if timestamp is later then target 81 ts, err := feeds.UpdatedAt(uch) 82 if err != nil { 83 return nil, err 84 } 85 if ts > at { 86 if e.isLeft() { 87 return ch, nil 88 } 89 return f.at(ctx, e.start-1, e.left(), ch) 90 } 91 if e.level == 0 { // matching update time or finest resolution 92 return uch, nil 93 } 94 // continue traversing based on at 95 return f.at(ctx, at, e.childAt(at), uch) 96 } 97 98 type result struct { 99 path *path 100 chunk swarm.Chunk 101 *epoch 102 } 103 104 // asyncFinder encapsulates a chunk store getter and a feed and provides 105 // non-concurrent lookup methods 106 type asyncFinder struct { 107 getter *feeds.Getter 108 } 109 110 type path struct { 111 at int64 112 top *result 113 bottom *result 114 cancel chan struct{} 115 } 116 117 func newPath(at int64) *path { 118 return &path{at, nil, nil, make(chan struct{})} 119 } 120 121 // NewAsyncFinder constructs an AsyncFinder 122 func NewAsyncFinder(getter storage.Getter, feed *feeds.Feed) feeds.Lookup { 123 return &asyncFinder{feeds.NewGetter(getter, feed)} 124 } 125 126 func (f *asyncFinder) get(ctx context.Context, at int64, e *epoch) (swarm.Chunk, error) { 127 u, err := f.getter.Get(ctx, e) 128 if err != nil { 129 if !errors.Is(err, storage.ErrNotFound) { 130 return nil, err 131 } 132 return nil, nil 133 } 134 ts, err := feeds.UpdatedAt(u) 135 if err != nil { 136 return nil, err 137 } 138 diff := at - int64(ts) 139 if diff < 0 { 140 return nil, nil 141 } 142 return u, nil 143 } 144 145 // at attempts to retrieve all epoch chunks on the path for `at` concurrently 146 func (f *asyncFinder) at(ctx context.Context, at int64, p *path, e *epoch, c chan<- *result) { 147 for ; ; e = e.childAt(uint64(at)) { 148 select { 149 case <-p.cancel: 150 return 151 default: 152 } 153 go func(e *epoch) { 154 uch, err := f.get(ctx, at, e) 155 if err != nil { 156 return 157 } 158 select { 159 case c <- &result{p, uch, e}: 160 case <-p.cancel: 161 } 162 }(e) 163 if e.level == 0 { 164 return 165 } 166 } 167 } 168 func (f *asyncFinder) At(ctx context.Context, at int64, after uint64) (swarm.Chunk, feeds.Index, feeds.Index, error) { 169 // TODO: current and next index return values need to be implemented 170 ch, err := f.asyncAt(ctx, at, after) 171 return ch, nil, nil, err 172 } 173 174 // At looks up the version valid at time `at` 175 // after is a unix time hint of the latest known update 176 func (f *asyncFinder) asyncAt(ctx context.Context, at int64, _ uint64) (swarm.Chunk, error) { 177 c := make(chan *result) 178 go f.at(ctx, at, newPath(at), &epoch{0, maxLevel}, c) 179 LOOP: 180 for r := range c { 181 p := r.path 182 // ignore result from paths already cancelled 183 select { 184 case <-p.cancel: 185 continue LOOP 186 default: 187 } 188 if r.chunk != nil { // update chunk for epoch found 189 if r.level == 0 { // return if deepest level epoch 190 return r.chunk, nil 191 } 192 // ignore if higher level than the deepest epoch found 193 if p.top != nil && p.top.level < r.level { 194 continue LOOP 195 } 196 p.top = r 197 } else { // update chunk for epoch not found 198 // if top level than return with no update found 199 if r.level == 32 { 200 close(p.cancel) 201 return nil, nil 202 } 203 // if topmost epoch not found, then set bottom 204 if p.bottom == nil || p.bottom.level < r.level { 205 p.bottom = r 206 } 207 } 208 209 // found - not found for two consecutive epochs 210 if p.top != nil && p.bottom != nil && p.top.level == p.bottom.level+1 { 211 // cancel path 212 close(p.cancel) 213 if p.bottom.isLeft() { 214 return p.top.chunk, nil 215 } 216 // recursive call on new path through left sister 217 np := newPath(at) 218 np.top = &result{np, p.top.chunk, p.top.epoch} 219 go f.at(ctx, int64(p.bottom.start-1), np, p.bottom.left(), c) 220 } 221 } 222 return nil, nil 223 }