github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/netstore_test.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  package storer_test
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/ethersphere/bee/v2/pkg/pushsync"
    15  	"github.com/ethersphere/bee/v2/pkg/retrieval"
    16  	storage "github.com/ethersphere/bee/v2/pkg/storage"
    17  	chunktesting "github.com/ethersphere/bee/v2/pkg/storage/testing"
    18  	storer "github.com/ethersphere/bee/v2/pkg/storer"
    19  	"github.com/ethersphere/bee/v2/pkg/swarm"
    20  )
    21  
    22  type testRetrieval struct {
    23  	fn func(swarm.Address) (swarm.Chunk, error)
    24  }
    25  
    26  func (t *testRetrieval) RetrieveChunk(_ context.Context, address swarm.Address, _ swarm.Address) (swarm.Chunk, error) {
    27  	return t.fn(address)
    28  }
    29  
    30  func testNetStore(t *testing.T, newStorer func(r retrieval.Interface) (*storer.DB, error)) {
    31  	t.Helper()
    32  
    33  	t.Run("direct upload", func(t *testing.T) {
    34  		t.Parallel()
    35  
    36  		t.Run("commit", func(t *testing.T) {
    37  			t.Parallel()
    38  
    39  			chunks := chunktesting.GenerateTestRandomChunks(10)
    40  
    41  			lstore, err := newStorer(nil)
    42  			if err != nil {
    43  				t.Fatal(err)
    44  			}
    45  
    46  			session := lstore.DirectUpload()
    47  
    48  			count := 0
    49  			quit := make(chan struct{})
    50  			t.Cleanup(func() { close(quit) })
    51  			go func() {
    52  				for {
    53  					select {
    54  					case op := <-lstore.PusherFeed():
    55  						found := false
    56  						for _, ch := range chunks {
    57  							if op.Chunk.Equal(ch) {
    58  								found = true
    59  								break
    60  							}
    61  						}
    62  						if !found {
    63  							op.Err <- fmt.Errorf("incorrect chunk for push: have %s", op.Chunk.Address())
    64  							continue
    65  						}
    66  						count++
    67  						op.Err <- nil
    68  					case <-quit:
    69  						return
    70  					}
    71  				}
    72  			}()
    73  
    74  			for _, ch := range chunks {
    75  				err := session.Put(context.TODO(), ch)
    76  				if err != nil {
    77  					t.Fatalf("session.Put(...): unexpected error: %v", err)
    78  				}
    79  			}
    80  
    81  			err = session.Done(chunks[0].Address())
    82  			if err != nil {
    83  				t.Fatalf("session.Done(): unexpected error: %v", err)
    84  			}
    85  
    86  			if count != 10 {
    87  				t.Fatalf("unexpected no of pusher ops want 10 have %d", count)
    88  			}
    89  
    90  			verifyChunks(t, lstore.Storage(), chunks, false)
    91  		})
    92  
    93  		t.Run("pusher error", func(t *testing.T) {
    94  			t.Parallel()
    95  
    96  			chunks := chunktesting.GenerateTestRandomChunks(10)
    97  
    98  			lstore, err := newStorer(nil)
    99  			if err != nil {
   100  				t.Fatal(err)
   101  			}
   102  
   103  			session := lstore.DirectUpload()
   104  
   105  			count := 0
   106  			quit := make(chan struct{})
   107  			t.Cleanup(func() { close(quit) })
   108  			wantErr := errors.New("dummy error")
   109  			go func() {
   110  				for {
   111  					select {
   112  					case op := <-lstore.PusherFeed():
   113  						found := false
   114  						for _, ch := range chunks {
   115  							if op.Chunk.Equal(ch) {
   116  								found = true
   117  								break
   118  							}
   119  						}
   120  						if !found {
   121  							op.Err <- fmt.Errorf("incorrect chunk for push: have %s", op.Chunk.Address())
   122  							continue
   123  						}
   124  						count++
   125  						if count >= 5 {
   126  							op.Err <- wantErr
   127  						} else {
   128  							op.Err <- nil
   129  						}
   130  					case <-quit:
   131  						return
   132  					}
   133  				}
   134  			}()
   135  
   136  			for _, ch := range chunks {
   137  				err := session.Put(context.TODO(), ch)
   138  				if err != nil && !errors.Is(err, wantErr) {
   139  					t.Fatalf("session.Put(...): unexpected error: %v", err)
   140  				}
   141  			}
   142  
   143  			err = session.Cleanup()
   144  			if err != nil {
   145  				t.Fatalf("session.Cleanup(): unexpected error: %v", err)
   146  			}
   147  
   148  			verifyChunks(t, lstore.Storage(), chunks, false)
   149  		})
   150  
   151  		t.Run("context cancellation", func(t *testing.T) {
   152  			t.Parallel()
   153  
   154  			chunks := chunktesting.GenerateTestRandomChunks(10)
   155  
   156  			lstore, err := newStorer(nil)
   157  			if err != nil {
   158  				t.Fatal(err)
   159  			}
   160  
   161  			session := lstore.DirectUpload()
   162  
   163  			ctx, cancel := context.WithCancel(context.Background())
   164  
   165  			count := 0
   166  			go func() {
   167  				<-lstore.PusherFeed()
   168  				count++
   169  				cancel()
   170  			}()
   171  
   172  			for _, ch := range chunks {
   173  				err := session.Put(ctx, ch)
   174  				if err != nil && !errors.Is(err, context.Canceled) {
   175  					t.Fatalf("session.Put(...): unexpected error: have %v", err)
   176  				}
   177  			}
   178  
   179  			err = session.Cleanup()
   180  			if err != nil {
   181  				t.Fatalf("session.Cleanup(): unexpected error: %v", err)
   182  			}
   183  
   184  			if count != 1 {
   185  				t.Fatalf("unexpected no of pusher ops want 5 have %d", count)
   186  			}
   187  
   188  			verifyChunks(t, lstore.Storage(), chunks, false)
   189  		})
   190  
   191  		t.Run("shallow receipt retry", func(t *testing.T) {
   192  			t.Parallel()
   193  
   194  			chunk := chunktesting.GenerateTestRandomChunk()
   195  
   196  			lstore, err := newStorer(nil)
   197  			if err != nil {
   198  				t.Fatal(err)
   199  			}
   200  
   201  			count := 3
   202  			go func() {
   203  				for op := range lstore.PusherFeed() {
   204  					if !op.Chunk.Equal(chunk) {
   205  						op.Err <- fmt.Errorf("incorrect chunk for push: have %s", op.Chunk.Address())
   206  						continue
   207  					}
   208  					if count > 0 {
   209  						count--
   210  						op.Err <- pushsync.ErrShallowReceipt
   211  					} else {
   212  						op.Err <- nil
   213  					}
   214  				}
   215  			}()
   216  
   217  			session := lstore.DirectUpload()
   218  
   219  			err = session.Put(context.Background(), chunk)
   220  			if err != nil {
   221  				t.Fatalf("session.Put(...): unexpected error: %v", err)
   222  			}
   223  
   224  			err = session.Done(chunk.Address())
   225  			if err != nil {
   226  				t.Fatalf("session.Done(): unexpected error: %v", err)
   227  			}
   228  
   229  			if count != 0 {
   230  				t.Fatalf("unexpected no of pusher ops want 0 have %d", count)
   231  			}
   232  		})
   233  
   234  		t.Run("download", func(t *testing.T) {
   235  			t.Parallel()
   236  
   237  			t.Run("with cache", func(t *testing.T) {
   238  				t.Parallel()
   239  
   240  				chunks := chunktesting.GenerateTestRandomChunks(10)
   241  
   242  				lstore, err := newStorer(&testRetrieval{fn: func(address swarm.Address) (swarm.Chunk, error) {
   243  					for _, ch := range chunks[5:] {
   244  						if ch.Address().Equal(address) {
   245  							return ch, nil
   246  						}
   247  					}
   248  					return nil, storage.ErrNotFound
   249  				}})
   250  				if err != nil {
   251  					t.Fatal(err)
   252  				}
   253  
   254  				// Add some chunks to Cache to simulate local retrieval.
   255  				for idx, ch := range chunks {
   256  					if idx < 5 {
   257  						err := lstore.Cache().Put(context.TODO(), ch)
   258  						if err != nil {
   259  							t.Fatalf("cache.Put(...): unexpected error: %v", err)
   260  						}
   261  					} else {
   262  						break
   263  					}
   264  				}
   265  
   266  				getter := lstore.Download(true)
   267  
   268  				for idx, ch := range chunks {
   269  					readCh, err := getter.Get(context.TODO(), ch.Address())
   270  					if err != nil {
   271  						t.Fatalf("download.Get(...): unexpected error: %v idx %d", err, idx)
   272  					}
   273  					if !readCh.Equal(ch) {
   274  						t.Fatalf("incorrect chunk read: address %s", readCh.Address())
   275  					}
   276  				}
   277  
   278  				t.Cleanup(lstore.WaitForBgCacheWorkers())
   279  
   280  				// After download is complete all chunks should be in the local storage.
   281  				verifyChunks(t, lstore.Storage(), chunks, true)
   282  			})
   283  		})
   284  
   285  		t.Run("no cache", func(t *testing.T) {
   286  			t.Parallel()
   287  
   288  			chunks := chunktesting.GenerateTestRandomChunks(10)
   289  
   290  			lstore, err := newStorer(&testRetrieval{fn: func(address swarm.Address) (swarm.Chunk, error) {
   291  				for _, ch := range chunks[5:] {
   292  					if ch.Address().Equal(address) {
   293  						return ch, nil
   294  					}
   295  				}
   296  				return nil, storage.ErrNotFound
   297  			}})
   298  			if err != nil {
   299  				t.Fatal(err)
   300  			}
   301  
   302  			// Add some chunks to Cache to simulate local retrieval.
   303  			for idx, ch := range chunks {
   304  				if idx < 5 {
   305  					err := lstore.Cache().Put(context.TODO(), ch)
   306  					if err != nil {
   307  						t.Fatalf("cache.Put(...): unexpected error: %v", err)
   308  					}
   309  				} else {
   310  					break
   311  				}
   312  			}
   313  
   314  			getter := lstore.Download(false)
   315  
   316  			for _, ch := range chunks {
   317  				readCh, err := getter.Get(context.TODO(), ch.Address())
   318  				if err != nil {
   319  					t.Fatalf("download.Get(...): unexpected error: %v", err)
   320  				}
   321  				if !readCh.Equal(ch) {
   322  					t.Fatalf("incorrect chunk read: address %s", readCh.Address())
   323  				}
   324  			}
   325  
   326  			// only the chunks that were already in cache should be present
   327  			verifyChunks(t, lstore.Storage(), chunks[:5], true)
   328  			verifyChunks(t, lstore.Storage(), chunks[5:], false)
   329  		})
   330  	})
   331  }
   332  
   333  func TestNetStore(t *testing.T) {
   334  	t.Parallel()
   335  
   336  	t.Run("inmem", func(t *testing.T) {
   337  		t.Parallel()
   338  
   339  		testNetStore(t, func(r retrieval.Interface) (*storer.DB, error) {
   340  
   341  			opts := dbTestOps(swarm.RandAddress(t), 0, nil, nil, time.Second)
   342  			opts.CacheCapacity = 100
   343  
   344  			db, err := storer.New(context.Background(), "", opts)
   345  			if err == nil {
   346  				db.SetRetrievalService(r)
   347  			}
   348  			return db, err
   349  		})
   350  	})
   351  	t.Run("disk", func(t *testing.T) {
   352  		t.Parallel()
   353  
   354  		testNetStore(t, func(r retrieval.Interface) (*storer.DB, error) {
   355  			opts := dbTestOps(swarm.RandAddress(t), 0, nil, nil, time.Second)
   356  
   357  			db, err := diskStorer(t, opts)()
   358  			if err == nil {
   359  				db.SetRetrievalService(r)
   360  			}
   361  			return db, err
   362  		})
   363  	})
   364  }