github.com/filecoin-project/lassie@v0.23.0/pkg/internal/itest/client_retrieval_test.go (about)

     1  package itest
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"math/rand"
     7  	"testing"
     8  	"time"
     9  
    10  	datatransfer "github.com/filecoin-project/go-data-transfer/v2"
    11  	retrievaltypes "github.com/filecoin-project/go-retrieval-types"
    12  	"github.com/filecoin-project/go-state-types/big"
    13  	"github.com/filecoin-project/lassie/pkg/internal/itest/mocknet"
    14  	"github.com/filecoin-project/lassie/pkg/net/client"
    15  	"github.com/ipfs/boxo/blockstore"
    16  	"github.com/ipfs/go-cid"
    17  	"github.com/ipfs/go-datastore"
    18  	"github.com/ipfs/go-datastore/namespace"
    19  	dss "github.com/ipfs/go-datastore/sync"
    20  	"github.com/ipfs/go-graphsync/storeutil"
    21  	"github.com/ipfs/go-unixfsnode"
    22  	unixfs "github.com/ipfs/go-unixfsnode/testutil"
    23  	"github.com/ipld/go-ipld-prime/linking"
    24  	selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func TestRetrieval(t *testing.T) {
    29  	rndSeed := time.Now().UTC().UnixNano()
    30  	t.Logf("random seed: %d", rndSeed)
    31  	var rndReader io.Reader = rand.New(rand.NewSource(rndSeed))
    32  
    33  	tests := []struct {
    34  		name     string
    35  		generate func(*testing.T, linking.LinkSystem) (srcData unixfs.DirEntry)
    36  	}{
    37  		{
    38  			name: "UnixFSFileDAG",
    39  			generate: func(t *testing.T, linkSystem linking.LinkSystem) unixfs.DirEntry {
    40  				return unixfs.GenerateFile(t, &linkSystem, rndReader, 4<<20)
    41  			},
    42  		},
    43  		{
    44  			name: "UnixFSDirectoryDAG",
    45  			generate: func(t *testing.T, linkSystem linking.LinkSystem) unixfs.DirEntry {
    46  				return unixfs.GenerateDirectory(t, &linkSystem, rndReader, 16<<20, false)
    47  			},
    48  		},
    49  		{
    50  			name: "UnixFSShardedDirectoryDAG",
    51  			generate: func(t *testing.T, linkSystem linking.LinkSystem) unixfs.DirEntry {
    52  				return unixfs.GenerateDirectory(t, &linkSystem, rndReader, 16<<20, true)
    53  			},
    54  		},
    55  	}
    56  
    57  	for _, tt := range tests {
    58  		t.Run(tt.name, func(t *testing.T) {
    59  			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    60  			defer cancel()
    61  
    62  			// Setup mocknet
    63  			mrn := mocknet.NewMockRetrievalNet(ctx, t)
    64  			mrn.AddGraphsyncPeers(1)
    65  			finishedChan := mocknet.SetupRetrieval(t, mrn.Remotes[0])
    66  			mrn.MN.LinkAll()
    67  
    68  			// Generate source data on the remote
    69  			srcData := tt.generate(t, *mrn.Remotes[0].LinkSystem)
    70  
    71  			// Perform retrieval
    72  			linkSystemLocal := runRetrieval(t, ctx, mrn, srcData.Root, finishedChan)
    73  
    74  			// Check retrieved data by loading it from the blockstore via UnixFS so we
    75  			// reify the original single file data from the DAG
    76  			linkSystemLocal.NodeReifier = unixfsnode.Reify
    77  			// Convert to []DirEntry slice
    78  			gotDir := unixfs.ToDirEntry(t, linkSystemLocal, srcData.Root, true)
    79  
    80  			// Validate data
    81  			unixfs.CompareDirEntries(t, srcData, gotDir)
    82  		})
    83  	}
    84  }
    85  
    86  func runRetrieval(t *testing.T, ctx context.Context, mrn *mocknet.MockRetrievalNet, rootCid cid.Cid, finishedChan chan []datatransfer.Event) linking.LinkSystem {
    87  	req := require.New(t)
    88  
    89  	// Setup local datastore and blockstore
    90  	dsLocal := dss.MutexWrap(datastore.NewMapDatastore())
    91  	dtDsLocal := namespace.Wrap(dsLocal, datastore.NewKey("datatransfer"))
    92  	bsLocal := blockstore.NewBlockstore(namespace.Wrap(dsLocal, datastore.NewKey("blockstore")))
    93  	linkSystemLocal := storeutil.LinkSystemForBlockstore(bsLocal)
    94  
    95  	// New client
    96  	client, err := client.NewClient(ctx, dtDsLocal, mrn.Self)
    97  	req.NoError(err)
    98  	req.NoError(client.AwaitReady())
    99  
   100  	// Collect events & stats
   101  	selfEvents := make([]datatransfer.Event, 0)
   102  	var lastReceivedBytes uint64
   103  	var lastReceivedBlocks uint64
   104  	cleanupChan := make(chan struct{}, 1)
   105  	subscriberLocal := func(event datatransfer.Event, channelState datatransfer.ChannelState) {
   106  		selfEvents = append(selfEvents, event)
   107  		lastReceivedBytes = channelState.Received()
   108  		lastReceivedBlocks = uint64(channelState.ReceivedCidsTotal())
   109  		if event.Code == datatransfer.CleanupComplete {
   110  			cleanupChan <- struct{}{}
   111  		}
   112  	}
   113  
   114  	// Retrieve
   115  	proposal := &retrievaltypes.DealProposal{
   116  		PayloadCID: rootCid,
   117  		ID:         retrievaltypes.DealID(100),
   118  		Params: retrievaltypes.Params{
   119  			PricePerByte: big.Zero(),
   120  			UnsealPrice:  big.Zero(),
   121  			Selector:     retrievaltypes.CborGenCompatibleNode{Node: selectorparse.CommonSelector_ExploreAllRecursively},
   122  		},
   123  	}
   124  	shutdown := make(chan struct{})
   125  	stats, err := client.RetrieveFromPeer(
   126  		ctx,
   127  		linkSystemLocal,
   128  		mrn.Remotes[0].Host.ID(),
   129  		proposal,
   130  		selectorparse.CommonSelector_ExploreAllRecursively,
   131  		0,
   132  		subscriberLocal,
   133  		shutdown,
   134  	)
   135  	req.NoError(err)
   136  	req.NotNil(stats)
   137  
   138  	// Ensure we are properly cleaned up
   139  	req.Eventually(func() bool {
   140  		select {
   141  		case <-cleanupChan:
   142  			return true
   143  		case <-ctx.Done():
   144  			require.Fail(t, ctx.Err().Error())
   145  			return false
   146  		default:
   147  			return false
   148  		}
   149  	}, 1*time.Second, 100*time.Millisecond)
   150  	remoteEvents := mocknet.WaitForFinish(ctx, t, finishedChan, 1*time.Second)
   151  
   152  	// Check stats
   153  	req.Equal(lastReceivedBytes, stats.Size)
   154  	req.Equal(lastReceivedBlocks, stats.Blocks)
   155  	req.Equal(0, stats.NumPayments)
   156  	req.Equal(rootCid, stats.RootCid)
   157  	req.True(stats.Duration > 0)
   158  	req.True(stats.TimeToFirstByte <= stats.Duration)
   159  
   160  	// Check events
   161  	req.Len(eventSliceFilter(selfEvents, datatransfer.Error), 0)
   162  	req.Len(eventSliceFilter(selfEvents, datatransfer.Open), 1)
   163  	req.Len(eventSliceFilter(selfEvents, datatransfer.Opened), 1)
   164  	req.Len(eventSliceFilter(selfEvents, datatransfer.TransferInitiated), 1)
   165  	req.Len(eventSliceFilter(selfEvents, datatransfer.Accept), 1)
   166  	req.Len(eventSliceFilter(selfEvents, datatransfer.ResumeResponder), 1)
   167  	req.Len(eventSliceFilter(selfEvents, datatransfer.FinishTransfer), 1)
   168  	req.Len(eventSliceFilter(selfEvents, datatransfer.CleanupComplete), 1)
   169  
   170  	// Check remote events
   171  	req.Len(eventSliceFilter(remoteEvents, datatransfer.Error), 0)
   172  	req.Len(eventSliceFilter(remoteEvents, datatransfer.Open), 1)
   173  	req.Len(eventSliceFilter(remoteEvents, datatransfer.Accept), 1)
   174  	req.Len(eventSliceFilter(remoteEvents, datatransfer.TransferInitiated), 1)
   175  	req.Len(eventSliceFilter(remoteEvents, datatransfer.CleanupComplete), 1)
   176  	// TODO: not reliably received, why? req.Len(eventSliceFilter(remoteEvents, datatransfer.Complete), 1)
   177  
   178  	return linkSystemLocal
   179  }
   180  
   181  func eventSliceFilter(events []datatransfer.Event, code datatransfer.EventCode) []datatransfer.Event {
   182  	filtered := make([]datatransfer.Event, 0)
   183  	for _, event := range events {
   184  		if event.Code == code {
   185  			filtered = append(filtered, event)
   186  		}
   187  	}
   188  	return filtered
   189  }