github.com/ethersphere/bee/v2@v2.2.0/pkg/replicas/getter.go (about)

     1  // Copyright 2023 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  // the code below implements the integration of dispersed replicas in chunk fetching.
     6  // using storage.Getter interface.
     7  package replicas
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/ethersphere/bee/v2/pkg/file/redundancy"
    16  	"github.com/ethersphere/bee/v2/pkg/soc"
    17  	"github.com/ethersphere/bee/v2/pkg/storage"
    18  	"github.com/ethersphere/bee/v2/pkg/swarm"
    19  )
    20  
    21  // ErrSwarmageddon is returned in case of a vis mayor called Swarmageddon.
    22  // Swarmageddon is the situation when none of the replicas can be retrieved.
    23  // If 2^{depth} replicas were uploaded and they all have valid postage stamps
    24  // then the probability of Swarmageddon is less than 0.000001
    25  // assuming the error rate of chunk retrievals stays below the level expressed
    26  // as depth by the publisher.
    27  var ErrSwarmageddon = errors.New("swarmageddon has begun")
    28  
    29  // getter is the private implementation of storage.Getter, an interface for
    30  // retrieving chunks. This getter embeds the original simple chunk getter and extends it
    31  // to a multiplexed variant that fetches chunks with replicas.
    32  //
    33  // the strategy to retrieve a chunk that has replicas can be configured with a few parameters:
    34  //   - RetryInterval: the delay before a new batch of replicas is fetched.
    35  //   - depth: 2^{depth} is the total number of additional replicas that have been uploaded
    36  //     (by default, it is assumed to be 4, ie. total of 16)
    37  //   - (not implemented) pivot: replicas with address in the proximity of pivot will be tried first
    38  type getter struct {
    39  	wg sync.WaitGroup
    40  	storage.Getter
    41  	level redundancy.Level
    42  }
    43  
    44  // NewGetter is the getter constructor
    45  func NewGetter(g storage.Getter, level redundancy.Level) storage.Getter {
    46  	return &getter{Getter: g, level: level}
    47  }
    48  
    49  // Get makes the getter satisfy the storage.Getter interface
    50  func (g *getter) Get(ctx context.Context, addr swarm.Address) (ch swarm.Chunk, err error) {
    51  	ctx, cancel := context.WithCancel(ctx)
    52  	defer cancel()
    53  
    54  	// channel that the results (retrieved chunks) are gathered to from concurrent
    55  	// workers each fetching a replica
    56  	resultC := make(chan swarm.Chunk)
    57  	// errc collects the errors
    58  	errc := make(chan error, 17)
    59  	var errs error
    60  	errcnt := 0
    61  
    62  	// concurrently call to retrieve chunk using original CAC address
    63  	g.wg.Add(1)
    64  	go func() {
    65  		defer g.wg.Done()
    66  		ch, err := g.Getter.Get(ctx, addr)
    67  		if err != nil {
    68  			errc <- err
    69  			return
    70  		}
    71  
    72  		select {
    73  		case resultC <- ch:
    74  		case <-ctx.Done():
    75  		}
    76  	}()
    77  	// counters
    78  	n := 0      // counts the replica addresses tried
    79  	target := 2 // the number of replicas attempted to download in this batch
    80  	total := g.level.GetReplicaCount()
    81  
    82  	//
    83  	rr := newReplicator(addr, g.level)
    84  	next := rr.c
    85  	var wait <-chan time.Time // nil channel to disable case
    86  	// addresses used are doubling each period of search expansion
    87  	// (at intervals of RetryInterval)
    88  	ticker := time.NewTicker(RetryInterval)
    89  	defer ticker.Stop()
    90  	for level := uint8(0); level <= uint8(g.level); {
    91  		select {
    92  		// at least one chunk is retrieved, cancel the rest and return early
    93  		case chunk := <-resultC:
    94  			cancel()
    95  			return chunk, nil
    96  
    97  		case err = <-errc:
    98  			errs = errors.Join(errs, err)
    99  			errcnt++
   100  			if errcnt > total {
   101  				return nil, errors.Join(ErrSwarmageddon, errs)
   102  			}
   103  
   104  			// ticker switches on the address channel
   105  		case <-wait:
   106  			wait = nil
   107  			next = rr.c
   108  			level++
   109  			target = 1 << level
   110  			n = 0
   111  			continue
   112  
   113  			// getting the addresses in order
   114  		case so := <-next:
   115  			if so == nil {
   116  				next = nil
   117  				continue
   118  			}
   119  
   120  			g.wg.Add(1)
   121  			go func() {
   122  				defer g.wg.Done()
   123  				ch, err := g.Getter.Get(ctx, swarm.NewAddress(so.addr))
   124  				if err != nil {
   125  					errc <- err
   126  					return
   127  				}
   128  
   129  				soc, err := soc.FromChunk(ch)
   130  				if err != nil {
   131  					errc <- err
   132  					return
   133  				}
   134  
   135  				select {
   136  				case resultC <- soc.WrappedChunk():
   137  				case <-ctx.Done():
   138  				}
   139  			}()
   140  			n++
   141  			if n < target {
   142  				continue
   143  			}
   144  			next = nil
   145  			wait = ticker.C
   146  		}
   147  	}
   148  
   149  	return nil, nil
   150  }