github.com/ethersphere/bee/v2@v2.2.0/pkg/feeds/sequence/sequence.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 sequence provides implementation of sequential indexing for 6 // time-based feeds 7 // this feed type is best suited for 8 // - version updates 9 // - followed updates 10 // - frequent or regular-interval updates 11 package sequence 12 13 import ( 14 "context" 15 "encoding/binary" 16 "errors" 17 "strconv" 18 "sync" 19 "time" 20 21 "github.com/ethersphere/bee/v2/pkg/crypto" 22 "github.com/ethersphere/bee/v2/pkg/feeds" 23 storage "github.com/ethersphere/bee/v2/pkg/storage" 24 "github.com/ethersphere/bee/v2/pkg/swarm" 25 ) 26 27 // DefaultLevels is the number of concurrent lookaheads 28 // 8 spans 2^8 updates 29 const DefaultLevels = 8 30 31 var ( 32 _ feeds.Index = (*index)(nil) 33 _ feeds.Lookup = (*finder)(nil) 34 _ feeds.Lookup = (*asyncFinder)(nil) 35 _ feeds.Updater = (*updater)(nil) 36 ) 37 38 // index just wraps a uint64. implements the feeds.Index interface 39 type index struct { 40 index uint64 41 } 42 43 func (i *index) String() string { 44 return strconv.FormatUint(i.index, 10) 45 } 46 47 func (i *index) MarshalBinary() ([]byte, error) { 48 indexBytes := make([]byte, 8) 49 binary.BigEndian.PutUint64(indexBytes, i.index) 50 return indexBytes, nil 51 } 52 53 // Next requires 54 func (i *index) Next(last int64, at uint64) feeds.Index { 55 return &index{i.index + 1} 56 } 57 58 // finder encapsulates a chunk store getter and a feed and provides 59 // non-concurrent lookup 60 type finder struct { 61 getter *feeds.Getter 62 } 63 64 // NewFinder constructs an finder (feeds.Lookup interface) 65 func NewFinder(getter storage.Getter, feed *feeds.Feed) feeds.Lookup { 66 return &finder{feeds.NewGetter(getter, feed)} 67 } 68 69 // At looks for incremental feed updates from 0 upwards, if it does not find one, it assumes that the last found update is the most recent 70 func (f *finder) At(ctx context.Context, at int64, _ uint64) (ch swarm.Chunk, current, next feeds.Index, err error) { 71 for i := uint64(0); ; i++ { 72 u, err := f.getter.Get(ctx, &index{i}) 73 if err != nil { 74 if !errors.Is(err, storage.ErrNotFound) { 75 return nil, nil, nil, err 76 } 77 if i > 0 { 78 current = &index{i - 1} 79 } 80 return ch, current, &index{i}, nil 81 } 82 ts, err := feeds.UpdatedAt(u) 83 if err != nil { 84 return nil, nil, nil, err 85 } 86 // if index is later than the `at` target index, then return previous chunk and index 87 if ts > uint64(at) { 88 return ch, &index{i - 1}, &index{i}, nil 89 } 90 ch = u 91 } 92 } 93 94 // asyncFinder encapsulates a chunk store getter and a feed and provides 95 // non-concurrent lookup 96 type asyncFinder struct { 97 getter *feeds.Getter 98 } 99 100 // NewAsyncFinder constructs an AsyncFinder 101 func NewAsyncFinder(getter storage.Getter, feed *feeds.Feed) feeds.Lookup { 102 return &asyncFinder{feeds.NewGetter(getter, feed)} 103 } 104 105 // interval represents a batch of concurrent retreieve requests 106 // that probe the interval (base,b+2^level) at offsets 2^k-1 for k=1,...,max 107 // recording the level of the latest found update chunk and the earliest not found update 108 // the actual latest update is guessed to be within a subinterval 109 type interval struct { 110 base uint64 // beginning of the interval, guaranteed to have an update 111 level int // maximum level to check 112 found *result // the result with the latest chunk found 113 notFound int // the earliest level where no update is found 114 } 115 116 // when a subinterval is identified to contain the latest update 117 // next returns an interval matching it 118 func (i *interval) next() *interval { 119 found := i.found.level 120 i.found.level = 0 121 return &interval{ 122 base: i.found.index, // set base to index of latest chunk found 123 level: found, // set max level to the latest update level 124 notFound: found, // set notFound to the latest update level 125 found: i.found, // inherit latest found result 126 } 127 } 128 129 func (i *interval) retry() *interval { 130 r := i.next() 131 r.level = i.level // reset to max 132 r.notFound = i.level // reset to max 133 return r 134 } 135 136 func newInterval(base uint64) *interval { 137 return &interval{base: base, level: DefaultLevels, notFound: DefaultLevels} 138 } 139 140 // results capture a chunk lookup on a interval 141 type result struct { 142 chunk swarm.Chunk // the chunk found 143 interval *interval // the interval it belongs to 144 level int // the level within the interval 145 index uint64 // the actual sequence index of the update 146 } 147 148 // At looks up the version valid at time `at` 149 // after is a unix time hint of the latest known update 150 func (f *asyncFinder) At(ctx context.Context, at int64, after uint64) (ch swarm.Chunk, cur, next feeds.Index, err error) { 151 // first lookup update at the 0 index 152 // TODO: consider receive after as uint 153 ch, err = f.get(ctx, at, after) 154 if err != nil { 155 return nil, nil, nil, err 156 } 157 if ch == nil { 158 return nil, nil, &index{after}, nil 159 } 160 // if chunk exists construct an initial interval with base=0 161 c := make(chan *result) 162 i := newInterval(0) 163 i.found = &result{ch, nil, 0, 0} 164 165 quit := make(chan struct{}) 166 defer close(quit) 167 168 // launch concurrent request at doubling intervals 169 go f.at(ctx, at, 0, i, c, quit) 170 for r := range c { 171 // collect the results into the interval 172 i = r.interval 173 if r.chunk == nil { 174 if i.notFound < r.level { 175 continue 176 } 177 i.notFound = r.level - 1 178 } else { 179 if i.found.level > r.level { 180 continue 181 } 182 // if a chunk is found on the max level, and this is already a subinterval 183 // then found.index+1 is already known to be not found 184 if i.level == r.level && r.level < DefaultLevels { 185 return r.chunk, &index{r.index}, &index{r.index + 1}, nil 186 } 187 i.found = r 188 } 189 // below applies even if i.latest==ceilingLevel in which case we just continue with 190 // DefaultLevel lookaheads 191 if i.found.level == i.notFound { 192 if i.found.level == 0 { 193 return i.found.chunk, &index{i.found.index}, &index{i.found.index + 1}, nil 194 } 195 go f.at(ctx, at, 0, i.next(), c, quit) 196 } 197 // inconsistent feed, retry 198 if i.notFound < i.found.level { 199 go f.at(ctx, at, i.found.level, i.retry(), c, quit) 200 } 201 } 202 return nil, nil, nil, nil 203 } 204 205 // at launches concurrent lookups at exponential intervals after the starting from further 206 func (f *asyncFinder) at(ctx context.Context, at int64, min int, i *interval, c chan<- *result, quit <-chan struct{}) { 207 var wg sync.WaitGroup 208 209 for l := i.level; l > min; l-- { 210 select { 211 case <-quit: // if the parent process quit 212 return 213 default: 214 } 215 216 wg.Add(1) 217 go func(l int) { 218 // TODO: remove hardcoded timeout and define it as constant or inject in the getter. 219 reqCtx, cancel := context.WithTimeout(ctx, 1*time.Second) 220 defer func() { 221 cancel() 222 wg.Done() 223 }() 224 index := i.base + (1 << l) - 1 225 chunk := f.asyncGet(reqCtx, at, index) 226 227 select { 228 case ch := <-chunk: 229 select { 230 case c <- &result{ch, i, l, index}: 231 case <-quit: 232 return 233 } 234 case <-reqCtx.Done(): 235 select { 236 case c <- &result{nil, i, l, index}: 237 case <-quit: 238 return 239 } 240 case <-quit: 241 } 242 }(l) 243 } 244 245 wg.Wait() 246 } 247 248 func (f *asyncFinder) asyncGet(ctx context.Context, at int64, index uint64) <-chan swarm.Chunk { 249 c := make(chan swarm.Chunk, 1) 250 go func() { 251 ch, err := f.get(ctx, at, index) 252 if err != nil { 253 return 254 } 255 c <- ch 256 }() 257 return c 258 } 259 260 // get performs a lookup of an update chunk, returns nil (not error) if not found 261 func (f *asyncFinder) get(ctx context.Context, at int64, idx uint64) (swarm.Chunk, error) { 262 u, err := f.getter.Get(ctx, &index{idx}) 263 if err != nil { 264 if !errors.Is(err, storage.ErrNotFound) { 265 return nil, err 266 } 267 // if 'not-found' error, then just silence and return nil chunk 268 return nil, nil 269 } 270 ts, err := feeds.UpdatedAt(u) 271 if err != nil { 272 return nil, err 273 } 274 // this means the update timestamp is later than the pivot time we are looking for 275 // handled as if the update was missing but with no uncertainty due to timeout 276 if at < int64(ts) { 277 return nil, nil 278 } 279 return u, nil 280 } 281 282 // updater encapsulates a feeds putter to generate successive updates for epoch based feeds 283 // it persists the last update 284 type updater struct { 285 *feeds.Putter 286 next uint64 287 } 288 289 // NewUpdater constructs a feed updater 290 func NewUpdater(putter storage.Putter, signer crypto.Signer, topic []byte) (feeds.Updater, error) { 291 p, err := feeds.NewPutter(putter, signer, topic) 292 if err != nil { 293 return nil, err 294 } 295 return &updater{Putter: p}, nil 296 } 297 298 // Update pushes an update to the feed through the chunk stores 299 func (u *updater) Update(ctx context.Context, at int64, payload []byte) error { 300 err := u.Put(ctx, &index{u.next}, at, payload) 301 if err != nil { 302 return err 303 } 304 u.next++ 305 return nil 306 } 307 308 func (u *updater) Feed() *feeds.Feed { 309 return u.Putter.Feed 310 }