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  }