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 }