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 }