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  }