github.com/ethersphere/bee/v2@v2.2.0/pkg/puller/puller_test.go (about)

     1  // Copyright 2020 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 puller_test
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/ethersphere/bee/v2/pkg/log"
    15  	"github.com/ethersphere/bee/v2/pkg/puller"
    16  	"github.com/ethersphere/bee/v2/pkg/puller/intervalstore"
    17  	mockps "github.com/ethersphere/bee/v2/pkg/pullsync/mock"
    18  	"github.com/ethersphere/bee/v2/pkg/spinlock"
    19  	"github.com/ethersphere/bee/v2/pkg/statestore/leveldb"
    20  	"github.com/ethersphere/bee/v2/pkg/statestore/mock"
    21  	"github.com/ethersphere/bee/v2/pkg/storage"
    22  	resMock "github.com/ethersphere/bee/v2/pkg/storer/mock"
    23  	"github.com/ethersphere/bee/v2/pkg/swarm"
    24  	kadMock "github.com/ethersphere/bee/v2/pkg/topology/kademlia/mock"
    25  	"github.com/ethersphere/bee/v2/pkg/util/testutil"
    26  	"github.com/google/go-cmp/cmp"
    27  )
    28  
    29  // test that adding one peer starts syncing
    30  func TestOneSync(t *testing.T) {
    31  	t.Parallel()
    32  
    33  	var (
    34  		addr    = swarm.RandAddress(t)
    35  		cursors = []uint64{1000, 1000, 1000}
    36  		replies = []mockps.SyncReply{
    37  			{Bin: 1, Start: 1, Topmost: 1000, Peer: addr},
    38  			{Bin: 2, Start: 1, Topmost: 1001, Peer: addr}}
    39  	)
    40  
    41  	_, _, kad, pullsync := newPuller(t, opts{
    42  		kad: []kadMock.Option{
    43  			kadMock.WithEachPeerRevCalls(
    44  				kadMock.AddrTuple{Addr: addr, PO: 1},
    45  			),
    46  		},
    47  		pullSync: []mockps.Option{mockps.WithCursors(cursors, 0), mockps.WithReplies(replies...)},
    48  		bins:     3,
    49  		rs:       resMock.NewReserve(resMock.WithRadius(1)),
    50  	})
    51  	time.Sleep(100 * time.Millisecond)
    52  
    53  	kad.Trigger()
    54  
    55  	waitCursorsCalled(t, pullsync, addr)
    56  	waitSyncCalledBins(t, pullsync, addr, 1, 2)
    57  }
    58  
    59  func TestSyncOutsideDepth(t *testing.T) {
    60  	t.Parallel()
    61  
    62  	var (
    63  		addr    = swarm.RandAddress(t)
    64  		addr2   = swarm.RandAddress(t)
    65  		cursors = []uint64{1000, 1000, 1000, 1000}
    66  		replies = []mockps.SyncReply{
    67  			{Bin: 0, Start: 1, Topmost: 1000, Peer: addr2},
    68  			{Bin: 2, Start: 1, Topmost: 1000, Peer: addr},
    69  			{Bin: 3, Start: 1, Topmost: 1000, Peer: addr}}
    70  	)
    71  
    72  	_, _, kad, pullsync := newPuller(t, opts{
    73  		kad: []kadMock.Option{
    74  			kadMock.WithEachPeerRevCalls(
    75  				kadMock.AddrTuple{Addr: addr, PO: 2},
    76  				kadMock.AddrTuple{Addr: addr2, PO: 0},
    77  			),
    78  		},
    79  		pullSync: []mockps.Option{mockps.WithCursors(cursors, 0), mockps.WithReplies(replies...)},
    80  		bins:     4,
    81  		rs:       resMock.NewReserve(resMock.WithRadius(2)),
    82  	})
    83  
    84  	time.Sleep(100 * time.Millisecond)
    85  	kad.Trigger()
    86  
    87  	waitCursorsCalled(t, pullsync, addr)
    88  	waitCursorsCalled(t, pullsync, addr2)
    89  
    90  	waitSyncCalledBins(t, pullsync, addr, 2, 3)
    91  	waitSyncCalledBins(t, pullsync, addr2, 0)
    92  }
    93  
    94  func TestSyncIntervals(t *testing.T) {
    95  	t.Parallel()
    96  
    97  	addr := swarm.RandAddress(t)
    98  
    99  	for _, tc := range []struct {
   100  		name      string   // name of test
   101  		cursors   []uint64 // mocked cursors to be exchanged from peer
   102  		replies   []mockps.SyncReply
   103  		intervals string // expected intervals on pivot
   104  	}{
   105  		{
   106  			name:      "0, 1 chunk on live",
   107  			cursors:   []uint64{0, 0},
   108  			intervals: "[[1 1]]",
   109  			replies: []mockps.SyncReply{
   110  				{Bin: 1, Start: 1, Topmost: 1, Peer: addr},
   111  				{Bin: 1, Start: 1, Topmost: 1, Peer: addr},
   112  			},
   113  		},
   114  		{
   115  			name:      "0 - calls 1-1, 2-5, 6-10",
   116  			cursors:   []uint64{0, 0},
   117  			intervals: "[[1 10]]",
   118  			replies: []mockps.SyncReply{
   119  				{Bin: 1, Start: 1, Topmost: 1, Peer: addr},
   120  				{Bin: 1, Start: 2, Topmost: 5, Peer: addr},
   121  				{Bin: 1, Start: 6, Topmost: 10, Peer: addr},
   122  			},
   123  		},
   124  		{
   125  			name:      "0, 1 - calls 1-1",
   126  			cursors:   []uint64{0, 1},
   127  			intervals: "[[1 1]]",
   128  			replies: []mockps.SyncReply{
   129  				{Bin: 1, Start: 1, Topmost: 1, Peer: addr},
   130  				{Bin: 1, Start: 1, Topmost: 1, Peer: addr},
   131  			},
   132  		},
   133  		{
   134  			name:      "0, 10 - calls 1-10",
   135  			cursors:   []uint64{0, 10},
   136  			intervals: "[[1 11]]",
   137  			replies: []mockps.SyncReply{
   138  				{Bin: 1, Start: 1, Topmost: 10, Peer: addr},
   139  				{Bin: 1, Start: 11, Topmost: 11, Peer: addr},
   140  			},
   141  		},
   142  		{
   143  			name:      "0, 50 - 1 call",
   144  			cursors:   []uint64{0, 50},
   145  			intervals: "[[1 50]]",
   146  			replies: []mockps.SyncReply{
   147  				{Bin: 1, Start: 1, Topmost: 50, Peer: addr},
   148  			},
   149  		},
   150  		{
   151  			name:      "0, 50 - 2 calls",
   152  			cursors:   []uint64{0, 50},
   153  			intervals: "[[1 51]]",
   154  			replies: []mockps.SyncReply{
   155  				{Bin: 1, Start: 1, Topmost: 50, Peer: addr},
   156  				{Bin: 1, Start: 51, Topmost: 51, Peer: addr},
   157  			},
   158  		},
   159  		{
   160  			name:      "1,100 - 2 calls",
   161  			cursors:   []uint64{130, 100},
   162  			intervals: "[[1 100]]",
   163  			replies: []mockps.SyncReply{
   164  				{Bin: 1, Start: 1, Topmost: 50, Peer: addr},
   165  				{Bin: 1, Start: 51, Topmost: 100, Peer: addr},
   166  			},
   167  		},
   168  		{
   169  			name:      "1,200 - 4 calls",
   170  			cursors:   []uint64{200, 200},
   171  			intervals: "[[1 200]]",
   172  			replies: []mockps.SyncReply{
   173  				{Bin: 1, Start: 1, Topmost: 1, Peer: addr},
   174  				{Bin: 1, Start: 2, Topmost: 100, Peer: addr},
   175  				{Bin: 1, Start: 101, Topmost: 150, Peer: addr},
   176  				{Bin: 1, Start: 151, Topmost: 200, Peer: addr},
   177  			},
   178  		},
   179  	} {
   180  		tc := tc
   181  		t.Run(tc.name, func(t *testing.T) {
   182  			t.Parallel()
   183  
   184  			_, st, kad, pullsync := newPuller(t, opts{
   185  				kad: []kadMock.Option{
   186  					kadMock.WithEachPeerRevCalls(
   187  						kadMock.AddrTuple{Addr: addr, PO: 1},
   188  					),
   189  				},
   190  				pullSync: []mockps.Option{mockps.WithCursors(tc.cursors, 0), mockps.WithReplies(tc.replies...)},
   191  				bins:     2,
   192  				rs:       resMock.NewReserve(resMock.WithRadius(1)),
   193  			})
   194  
   195  			time.Sleep(100 * time.Millisecond)
   196  			kad.Trigger()
   197  			waitSyncCalledBins(t, pullsync, addr, 1)
   198  			waitSyncStart(t, pullsync, addr, tc.replies[len(tc.replies)-1].Start)
   199  			time.Sleep(100 * time.Millisecond)
   200  			checkIntervals(t, st, addr, tc.intervals, 1)
   201  		})
   202  	}
   203  }
   204  
   205  func TestPeerDisconnected(t *testing.T) {
   206  	t.Parallel()
   207  
   208  	cursors := []uint64{0, 0, 0, 0, 0}
   209  	addr := swarm.RandAddress(t)
   210  
   211  	p, _, kad, pullsync := newPuller(t, opts{
   212  		kad: []kadMock.Option{
   213  			kadMock.WithEachPeerRevCalls(
   214  				kadMock.AddrTuple{Addr: addr, PO: 1},
   215  			),
   216  		},
   217  		pullSync: []mockps.Option{mockps.WithCursors(cursors, 0)},
   218  		bins:     5,
   219  		rs:       resMock.NewReserve(resMock.WithRadius(2)),
   220  	})
   221  
   222  	time.Sleep(100 * time.Millisecond)
   223  	kad.Trigger()
   224  	waitCursorsCalled(t, pullsync, addr)
   225  	if !p.IsSyncing(addr) {
   226  		t.Fatalf("peer is not syncing but should")
   227  	}
   228  	kad.ResetPeers()
   229  	kad.Trigger()
   230  	time.Sleep(50 * time.Millisecond)
   231  	if p.IsSyncing(addr) {
   232  		t.Fatalf("peer is syncing but shouldn't")
   233  	}
   234  }
   235  
   236  func TestEpochReset(t *testing.T) {
   237  	t.Parallel()
   238  
   239  	cursors := []uint64{0, 50}
   240  
   241  	beforeEpoch := 900
   242  	afterEpoch := 1000
   243  
   244  	addr := swarm.RandAddress(t)
   245  	s := mock.NewStateStore()
   246  
   247  	replies := []mockps.SyncReply{
   248  		{Bin: 1, Start: 1, Topmost: 50, Peer: addr},
   249  		{Bin: 1, Start: 1, Topmost: 50, Peer: addr},
   250  	}
   251  
   252  	peer := kadMock.AddrTuple{Addr: addr, PO: 1}
   253  
   254  	p, kad, pullsync := newPullerWithState(t, s, opts{
   255  		kad:      []kadMock.Option{kadMock.WithEachPeerRevCalls(peer)},
   256  		pullSync: []mockps.Option{mockps.WithCursors(cursors, uint64(beforeEpoch)), mockps.WithReplies(replies...)},
   257  		bins:     2,
   258  		rs:       resMock.NewReserve(resMock.WithRadius(1)),
   259  	})
   260  
   261  	time.Sleep(100 * time.Millisecond)
   262  	kad.Trigger()
   263  	waitSync(t, pullsync, addr)
   264  	if !p.IsSyncing(addr) {
   265  		t.Fatalf("peer is not syncing but should")
   266  	}
   267  	kad.ResetPeers()
   268  	kad.Trigger()
   269  	time.Sleep(100 * time.Millisecond)
   270  	if p.IsSyncing(addr) {
   271  		t.Fatalf("peer is syncing but shouldn't")
   272  	}
   273  
   274  	beforeCalls := pullsync.SyncCalls(addr)
   275  
   276  	pullsync.SetEpoch(uint64(afterEpoch))
   277  	pullsync.ResetCalls(addr)
   278  
   279  	kad.AddRevPeers(peer)
   280  	kad.Trigger()
   281  	waitSync(t, pullsync, addr)
   282  
   283  	afterCalls := pullsync.SyncCalls(addr)
   284  
   285  	// after resetting the epoch, the peer will resync all intervals again.
   286  	// Hence why the sync calls from before and now should be the same.
   287  	if diff := cmp.Diff(beforeCalls, afterCalls); diff != "" {
   288  		t.Fatalf("invalid calls (+want -have):\n%s", diff)
   289  	}
   290  }
   291  
   292  func TestBinReset(t *testing.T) {
   293  	t.Parallel()
   294  
   295  	var (
   296  		addr    = swarm.RandAddress(t)
   297  		cursors = []uint64{1000, 1000, 1000}
   298  	)
   299  
   300  	_, s, kad, pullsync := newPuller(t, opts{
   301  		kad: []kadMock.Option{
   302  			kadMock.WithEachPeerRevCalls(
   303  				kadMock.AddrTuple{Addr: addr, PO: 1},
   304  			),
   305  		},
   306  		pullSync: []mockps.Option{mockps.WithCursors(cursors, 0), mockps.WithReplies(mockps.SyncReply{Bin: 1, Start: 1, Topmost: 1, Peer: addr})},
   307  		bins:     3,
   308  		rs:       resMock.NewReserve(resMock.WithRadius(2)),
   309  	})
   310  
   311  	time.Sleep(100 * time.Millisecond)
   312  
   313  	kad.Trigger()
   314  
   315  	waitCursorsCalled(t, pullsync, addr)
   316  	waitSync(t, pullsync, addr)
   317  
   318  	kad.ResetPeers()
   319  	kad.Trigger()
   320  	time.Sleep(100 * time.Millisecond)
   321  
   322  	if err := s.Get(fmt.Sprintf("sync|001|%s", addr.ByteString()), nil); !errors.Is(err, storage.ErrNotFound) {
   323  		t.Fatalf("got error %v, want %v", err, storage.ErrNotFound)
   324  	}
   325  
   326  	if err := s.Get(fmt.Sprintf("sync|000|%s", addr.ByteString()), nil); !errors.Is(err, storage.ErrNotFound) {
   327  		t.Fatalf("got error %v, want %v", err, storage.ErrNotFound)
   328  	}
   329  }
   330  
   331  func TestRadiusDecreaseNeighbor(t *testing.T) {
   332  	t.Parallel()
   333  
   334  	base := swarm.RandAddress(t)
   335  	peerAddr := swarm.RandAddressAt(t, base, 2)
   336  
   337  	var (
   338  		cursors = []uint64{1000, 1000, 1000, 1000}
   339  		replies = []mockps.SyncReply{
   340  			{Bin: 0, Start: 1, Topmost: 1000, Peer: peerAddr},
   341  			{Bin: 1, Start: 1, Topmost: 1000, Peer: peerAddr},
   342  			{Bin: 2, Start: 1, Topmost: 1000, Peer: peerAddr},
   343  			{Bin: 3, Start: 1, Topmost: 1000, Peer: peerAddr},
   344  			{Bin: 0, Start: 1, Topmost: 1000, Peer: peerAddr},
   345  			{Bin: 1, Start: 1, Topmost: 1000, Peer: peerAddr},
   346  		}
   347  	)
   348  
   349  	// at first, sync all bins
   350  	rs := resMock.NewReserve(resMock.WithRadius(0))
   351  
   352  	_, _, kad, pullsync := newPulleAddr(t, base, opts{
   353  		kad: []kadMock.Option{
   354  			kadMock.WithEachPeerRevCalls(
   355  				kadMock.AddrTuple{Addr: peerAddr, PO: 2},
   356  			),
   357  		},
   358  		pullSync: []mockps.Option{mockps.WithCursors(cursors, 0), mockps.WithReplies(replies...)},
   359  		bins:     4,
   360  		rs:       rs,
   361  	})
   362  
   363  	waitSyncCalledBins(t, pullsync, peerAddr, 0, 1, 2, 3)
   364  
   365  	// sync all bins >= 2, as this peer is still within depth
   366  	rs.SetStorageRadius(2)
   367  	kad.Trigger()
   368  	time.Sleep(time.Millisecond * 250)
   369  
   370  	// peer is still within depth, resync bins < 2
   371  	pullsync.ResetCalls(swarm.ZeroAddress)
   372  	rs.SetStorageRadius(0)
   373  	kad.Trigger()
   374  	time.Sleep(time.Millisecond * 250)
   375  
   376  	waitSyncCalledBins(t, pullsync, peerAddr, 0, 1)
   377  }
   378  
   379  func TestRadiusDecreaseNonNeighbor(t *testing.T) {
   380  	t.Parallel()
   381  
   382  	base := swarm.RandAddress(t)
   383  	peerAddr := swarm.RandAddressAt(t, base, 1)
   384  
   385  	var (
   386  		cursors = []uint64{1000, 1000, 1000, 1000}
   387  		replies = []mockps.SyncReply{
   388  			{Bin: 0, Start: 1, Topmost: 1000, Peer: peerAddr},
   389  			{Bin: 1, Start: 1, Topmost: 1000, Peer: peerAddr},
   390  			{Bin: 2, Start: 1, Topmost: 1000, Peer: peerAddr},
   391  			{Bin: 3, Start: 1, Topmost: 1000, Peer: peerAddr},
   392  			{Bin: 0, Start: 1, Topmost: 1000, Peer: peerAddr},
   393  			{Bin: 1, Start: 1, Topmost: 1000, Peer: peerAddr},
   394  			{Bin: 2, Start: 1, Topmost: 1000, Peer: peerAddr},
   395  			{Bin: 3, Start: 1, Topmost: 1000, Peer: peerAddr},
   396  		}
   397  	)
   398  
   399  	// at first, sync all bins
   400  	rs := resMock.NewReserve(resMock.WithRadius(0))
   401  
   402  	_, _, kad, pullsync := newPulleAddr(t, base, opts{
   403  		kad: []kadMock.Option{
   404  			kadMock.WithEachPeerRevCalls(
   405  				kadMock.AddrTuple{Addr: peerAddr, PO: 2},
   406  			),
   407  		},
   408  		pullSync: []mockps.Option{mockps.WithCursors(cursors, 0), mockps.WithReplies(replies...)},
   409  		bins:     4,
   410  		rs:       rs,
   411  	})
   412  
   413  	waitSyncCalledBins(t, pullsync, peerAddr, 0, 1, 2, 3)
   414  
   415  	// syncs bin 2 only as this peer is out of depth
   416  	rs.SetStorageRadius(3)
   417  	kad.Trigger()
   418  	time.Sleep(time.Millisecond * 250)
   419  
   420  	// peer is now within depth, resync all bins
   421  	pullsync.ResetCalls(swarm.ZeroAddress)
   422  	rs.SetStorageRadius(0)
   423  	kad.Trigger()
   424  	time.Sleep(time.Millisecond * 250)
   425  
   426  	waitSyncCalledBins(t, pullsync, peerAddr, 0, 1, 2, 3)
   427  }
   428  
   429  func TestRadiusIncrease(t *testing.T) {
   430  	t.Parallel()
   431  
   432  	var (
   433  		addr    = swarm.RandAddress(t)
   434  		cursors = []uint64{1000, 1000, 1000, 1000}
   435  		replies = []mockps.SyncReply{
   436  			{Bin: 1, Start: 1, Topmost: 1000, Peer: addr},
   437  			{Bin: 2, Start: 1, Topmost: 1000, Peer: addr},
   438  			{Bin: 3, Start: 1, Topmost: 1000, Peer: addr},
   439  			{Bin: 1, Start: 1, Topmost: 1000, Peer: addr},
   440  		}
   441  	)
   442  
   443  	rs := resMock.NewReserve(resMock.WithRadius(1))
   444  
   445  	p, _, kad, pullsync := newPuller(t, opts{
   446  		kad: []kadMock.Option{
   447  			kadMock.WithEachPeerRevCalls(
   448  				kadMock.AddrTuple{Addr: addr, PO: 1},
   449  			),
   450  		},
   451  		pullSync: []mockps.Option{mockps.WithCursors(cursors, 0), mockps.WithReplies(replies...)},
   452  		bins:     4,
   453  		rs:       rs,
   454  	})
   455  
   456  	time.Sleep(100 * time.Millisecond)
   457  	kad.Trigger()
   458  	waitSyncCalledBins(t, pullsync, addr, 2, 3)
   459  
   460  	pullsync.ResetCalls(swarm.ZeroAddress)
   461  	rs.SetStorageRadius(2)
   462  	kad.Trigger()
   463  	time.Sleep(100 * time.Millisecond)
   464  	if !p.IsBinSyncing(addr, 1) {
   465  		t.Fatalf("peer is not syncing but should")
   466  	}
   467  	if p.IsBinSyncing(addr, 2) {
   468  		t.Fatalf("peer is syncing but shouldn't")
   469  	}
   470  }
   471  
   472  // TestContinueSyncing adds a single peer with PO 0 to hist and live sync only a peer
   473  // to test that when Sync returns an error, the syncing does not terminate.
   474  func TestContinueSyncing(t *testing.T) {
   475  	t.Parallel()
   476  
   477  	var (
   478  		addr = swarm.RandAddress(t)
   479  	)
   480  
   481  	_, _, kad, pullsync := newPuller(t, opts{
   482  		kad: []kadMock.Option{
   483  			kadMock.WithEachPeerRevCalls(kadMock.AddrTuple{Addr: addr, PO: 0}),
   484  		},
   485  		pullSync: []mockps.Option{
   486  			mockps.WithCursors([]uint64{1}, 0),
   487  			mockps.WithSyncError(errors.New("sync error")),
   488  			mockps.WithReplies(
   489  				mockps.SyncReply{Start: 1, Topmost: 2, Peer: addr},
   490  				mockps.SyncReply{Start: 1, Topmost: 2, Peer: addr},
   491  				mockps.SyncReply{Start: 1, Topmost: 2, Peer: addr},
   492  			),
   493  		},
   494  		bins: 1,
   495  		rs:   resMock.NewReserve(resMock.WithRadius(0)),
   496  
   497  		syncSleepDur: time.Millisecond * 10,
   498  	})
   499  
   500  	time.Sleep(100 * time.Millisecond)
   501  	kad.Trigger()
   502  
   503  	err := spinlock.Wait(time.Second, func() bool {
   504  		return len(pullsync.SyncCalls(addr)) == 1
   505  	})
   506  	if err != nil {
   507  		t.Fatal(err)
   508  	}
   509  }
   510  
   511  func TestPeerGone(t *testing.T) {
   512  	t.Parallel()
   513  
   514  	var (
   515  		addr    = swarm.RandAddress(t)
   516  		replies = []mockps.SyncReply{
   517  			{Bin: 0, Start: 1, Topmost: 1001, Peer: addr},
   518  			{Bin: 1, Start: 1, Topmost: 1001, Peer: addr}}
   519  	)
   520  
   521  	p, _, kad, pullsync := newPuller(t, opts{
   522  		kad: []kadMock.Option{
   523  			kadMock.WithEachPeerRevCalls(kadMock.AddrTuple{Addr: addr, PO: 1}),
   524  		},
   525  		pullSync: []mockps.Option{
   526  			mockps.WithCursors([]uint64{1, 1}, 0),
   527  			mockps.WithReplies(replies...),
   528  		},
   529  		bins: 2,
   530  		rs:   resMock.NewReserve(resMock.WithRadius(1)),
   531  
   532  		syncSleepDur: time.Millisecond * 10,
   533  	})
   534  
   535  	time.Sleep(100 * time.Millisecond)
   536  	kad.Trigger()
   537  	time.Sleep(100 * time.Millisecond)
   538  
   539  	beforeCalls := pullsync.SyncCalls(addr)
   540  
   541  	if len(beforeCalls) != 1 {
   542  		t.Fatalf("unexpected amount of calls, got %d, want 1", len(beforeCalls))
   543  	}
   544  
   545  	kad.ResetPeers()
   546  	kad.Trigger()
   547  	time.Sleep(100 * time.Millisecond)
   548  
   549  	afterCalls := pullsync.SyncCalls(addr)
   550  
   551  	if len(beforeCalls) != len(afterCalls) {
   552  		t.Fatalf("unexpected new calls to sync interval, expected 0, got %d", len(afterCalls)-len(beforeCalls))
   553  	}
   554  
   555  	if p.IsSyncing(addr) {
   556  		t.Fatalf("peer is syncing but shouldn't")
   557  	}
   558  }
   559  
   560  func checkIntervals(t *testing.T, s storage.StateStorer, addr swarm.Address, expInterval string, bin uint8) {
   561  	t.Helper()
   562  	key := puller.PeerIntervalKey(addr, bin)
   563  	i := &intervalstore.Intervals{}
   564  	err := s.Get(key, i)
   565  	if err != nil {
   566  		t.Fatalf("error getting interval for bin %d: %v", bin, err)
   567  	}
   568  	if v := i.String(); v != expInterval {
   569  		t.Fatalf("got unexpected interval: %s, want %s bin %d", v, expInterval, bin)
   570  	}
   571  }
   572  
   573  // waitCursorsCalled waits until GetCursors are called on the given address.
   574  func waitCursorsCalled(t *testing.T, ps *mockps.PullSyncMock, addr swarm.Address) {
   575  	t.Helper()
   576  
   577  	err := spinlock.Wait(time.Second, func() bool {
   578  		return ps.CursorsCalls(addr)
   579  	})
   580  	if err != nil {
   581  		t.Fatal("timed out waiting for cursors")
   582  	}
   583  }
   584  
   585  // waitSync waits until SyncInterval is called on the address given.
   586  func waitSync(t *testing.T, ps *mockps.PullSyncMock, addr swarm.Address) {
   587  	t.Helper()
   588  
   589  	err := spinlock.Wait(time.Second, func() bool {
   590  		v := ps.SyncCalls(addr)
   591  		return len(v) > 0
   592  	})
   593  	if err != nil {
   594  		t.Fatal("timed out waiting for sync")
   595  	}
   596  }
   597  
   598  // waitSyncCalled waits until SyncInterval is called on the address given.
   599  func waitSyncStart(t *testing.T, ps *mockps.PullSyncMock, addr swarm.Address, start uint64) {
   600  	t.Helper()
   601  
   602  	err := spinlock.Wait(time.Second, func() bool {
   603  		calls := ps.SyncCalls(addr)
   604  		for _, c := range calls {
   605  			if c.Start == start {
   606  				return true
   607  			}
   608  		}
   609  		return false
   610  	})
   611  	if err != nil {
   612  		t.Fatal("timed out waiting for sync")
   613  	}
   614  }
   615  
   616  func waitSyncCalledBins(t *testing.T, ps *mockps.PullSyncMock, addr swarm.Address, bins ...uint8) {
   617  	t.Helper()
   618  
   619  	err := spinlock.Wait(time.Second, func() bool {
   620  		calls := ps.SyncCalls(addr)
   621  	nextCall:
   622  		for _, b := range bins {
   623  			for _, c := range calls {
   624  				if c.Bin == b {
   625  					continue nextCall
   626  				}
   627  			}
   628  			return false
   629  		}
   630  		return true
   631  	})
   632  	if err != nil {
   633  		t.Fatal("timed out waiting for sync")
   634  	}
   635  }
   636  
   637  type opts struct {
   638  	pullSync     []mockps.Option
   639  	kad          []kadMock.Option
   640  	rs           *resMock.ReserveStore
   641  	bins         uint8
   642  	syncSleepDur time.Duration
   643  }
   644  
   645  func newPuller(t *testing.T, ops opts) (*puller.Puller, storage.StateStorer, *kadMock.Mock, *mockps.PullSyncMock) {
   646  	t.Helper()
   647  
   648  	logger := log.Noop
   649  
   650  	s, err := leveldb.NewStateStore(t.TempDir(), logger)
   651  	if err != nil {
   652  		t.Fatal(err)
   653  	}
   654  
   655  	ps := mockps.NewPullSync(ops.pullSync...)
   656  	kad := kadMock.NewMockKademlia(ops.kad...)
   657  
   658  	o := puller.Options{
   659  		Bins: ops.bins,
   660  	}
   661  	p := puller.New(swarm.RandAddress(t), s, kad, ops.rs, ps, nil, logger, o)
   662  	p.Start(context.Background())
   663  
   664  	testutil.CleanupCloser(t, p, s)
   665  
   666  	return p, s, kad, ps
   667  }
   668  
   669  func newPulleAddr(t *testing.T, addr swarm.Address, ops opts) (*puller.Puller, storage.StateStorer, *kadMock.Mock, *mockps.PullSyncMock) {
   670  	t.Helper()
   671  
   672  	logger := log.Noop
   673  
   674  	s, err := leveldb.NewStateStore(t.TempDir(), logger)
   675  	if err != nil {
   676  		t.Fatal(err)
   677  	}
   678  
   679  	ps := mockps.NewPullSync(ops.pullSync...)
   680  	kad := kadMock.NewMockKademlia(ops.kad...)
   681  
   682  	o := puller.Options{
   683  		Bins: ops.bins,
   684  	}
   685  	p := puller.New(addr, s, kad, ops.rs, ps, nil, logger, o)
   686  	p.Start(context.Background())
   687  
   688  	testutil.CleanupCloser(t, p, s)
   689  
   690  	return p, s, kad, ps
   691  }
   692  
   693  func newPullerWithState(t *testing.T, s storage.StateStorer, ops opts) (*puller.Puller, *kadMock.Mock, *mockps.PullSyncMock) {
   694  	t.Helper()
   695  
   696  	ps := mockps.NewPullSync(ops.pullSync...)
   697  	kad := kadMock.NewMockKademlia(ops.kad...)
   698  	logger := log.Noop
   699  
   700  	o := puller.Options{
   701  		Bins: ops.bins,
   702  	}
   703  	p := puller.New(swarm.RandAddress(t), s, kad, ops.rs, ps, nil, logger, o)
   704  	p.Start(context.Background())
   705  
   706  	testutil.CleanupCloser(t, p)
   707  
   708  	return p, kad, ps
   709  }