github.com/filecoin-project/lassie@v0.23.0/pkg/internal/itest/mocknet/mocknet.go (about) 1 package mocknet 2 3 import ( 4 "context" 5 "errors" 6 "sync" 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/lassie/pkg/internal/itest/testpeer" 13 "github.com/filecoin-project/lassie/pkg/types" 14 bsnet "github.com/ipfs/boxo/bitswap/network" 15 bssrv "github.com/ipfs/boxo/bitswap/server" 16 "github.com/ipfs/go-cid" 17 "github.com/ipld/go-ipld-prime" 18 "github.com/ipld/go-ipld-prime/datamodel" 19 cidlink "github.com/ipld/go-ipld-prime/linking/cid" 20 "github.com/ipni/go-libipni/metadata" 21 "github.com/libp2p/go-libp2p/core/host" 22 "github.com/libp2p/go-libp2p/core/peer" 23 lpmock "github.com/libp2p/go-libp2p/p2p/net/mock" 24 "github.com/multiformats/go-multicodec" 25 "github.com/stretchr/testify/require" 26 ) 27 28 var QueryErrorTriggerCid = cid.MustParse("bafkqaalb") 29 30 type MockRetrievalNet struct { 31 ctx context.Context 32 t *testing.T 33 testPeerGenerator testpeer.TestPeerGenerator 34 35 RemoteEvents [][]datatransfer.Event 36 FinishedChan []chan struct{} 37 MN lpmock.Mocknet 38 Self host.Host 39 Remotes []testpeer.TestPeer 40 Source types.CandidateSource 41 } 42 43 func NewMockRetrievalNet(ctx context.Context, t *testing.T) *MockRetrievalNet { 44 mrn := &MockRetrievalNet{ 45 ctx: ctx, 46 t: t, 47 Remotes: make([]testpeer.TestPeer, 0), 48 RemoteEvents: make([][]datatransfer.Event, 0), 49 FinishedChan: make([]chan struct{}, 0), 50 } 51 mrn.Source = &mockCandidateSource{mrn} 52 mrn.t.Cleanup(func() { 53 require.NoError(mrn.t, mrn.TearDown()) 54 }) 55 // Setup network 56 mrn.MN = lpmock.New() 57 mrn.testPeerGenerator = testpeer.NewTestPeerGenerator(mrn.ctx, mrn.t, mrn.MN, []bsnet.NetOpt{}, []bssrv.Option{}) 58 h, err := mrn.MN.GenPeer() 59 mrn.Self = h 60 require.NoError(mrn.t, err) 61 return mrn 62 } 63 64 func (mrn *MockRetrievalNet) AddBitswapPeers(n int, opts ...testpeer.PeerOption) { 65 mrn.addPeers(mrn.testPeerGenerator.BitswapPeers(n, opts...)) 66 } 67 68 func (mrn *MockRetrievalNet) AddGraphsyncPeers(n int, opts ...testpeer.PeerOption) { 69 mrn.addPeers(mrn.testPeerGenerator.GraphsyncPeers(n, opts...)) 70 } 71 72 func (mrn *MockRetrievalNet) AddHttpPeers(n int, opts ...testpeer.PeerOption) { 73 mrn.addPeers(mrn.testPeerGenerator.HttpPeers(n, opts...)) 74 } 75 76 func (mrn *MockRetrievalNet) addPeers(peers []testpeer.TestPeer) { 77 for i := 0; i < len(peers); i++ { 78 mrn.Remotes = append(mrn.Remotes, peers[i]) 79 mrn.RemoteEvents = append(mrn.RemoteEvents, make([]datatransfer.Event, 0)) 80 mrn.FinishedChan = append(mrn.FinishedChan, make(chan struct{}, 1)) 81 } 82 } 83 84 func SetupRetrieval(t *testing.T, remote testpeer.TestPeer) chan []datatransfer.Event { 85 // Register DealProposal voucher type with automatic Pull acceptance 86 remoteDealValidator := &mockDealValidator{t: t, acceptPull: true} 87 require.NoError(t, remote.DatatransferServer.RegisterVoucherType(retrievaltypes.DealProposalType, remoteDealValidator)) 88 89 remoteEvents := make([]datatransfer.Event, 0) 90 finishedChan := make(chan []datatransfer.Event, 1) 91 92 // Record remote events 93 subscriberRemote := func(event datatransfer.Event, channelState datatransfer.ChannelState) { 94 remoteEvents = append(remoteEvents, event) 95 if event.Code == datatransfer.CleanupComplete { 96 finishedChan <- remoteEvents 97 } 98 } 99 remote.DatatransferServer.SubscribeToEvents(subscriberRemote) 100 101 return finishedChan 102 } 103 104 func WaitForFinish(ctx context.Context, t *testing.T, finishChan chan []datatransfer.Event, timeout time.Duration) []datatransfer.Event { 105 var events []datatransfer.Event 106 require.Eventually(t, func() bool { 107 select { 108 case events = <-finishChan: 109 return true 110 case <-ctx.Done(): 111 require.Fail(t, ctx.Err().Error()) 112 return false 113 default: 114 return false 115 } 116 }, timeout, 100*time.Millisecond) 117 return events 118 } 119 120 func (mrn *MockRetrievalNet) TearDown() error { 121 var wg sync.WaitGroup 122 for _, h := range mrn.Remotes { 123 wg.Add(1) 124 go func(h testpeer.TestPeer) { 125 defer wg.Done() 126 if h.DatatransferServer != nil { 127 h.DatatransferServer.Stop(context.Background()) 128 } 129 if h.BitswapServer != nil { 130 h.BitswapServer.Close() 131 } 132 if h.BitswapNetwork != nil { 133 h.BitswapNetwork.Stop() 134 } 135 if h.HttpServer != nil { 136 h.HttpServer.Close() 137 } 138 }(h) 139 } 140 wg.Wait() 141 return mrn.MN.Close() 142 } 143 144 type mockCandidateSource struct { 145 mrn *MockRetrievalNet 146 } 147 148 func (mcf *mockCandidateSource) findCandidates(ctx context.Context, cid cid.Cid) ([]types.RetrievalCandidate, error) { 149 candidates := make([]types.RetrievalCandidate, 0) 150 for _, h := range mcf.mrn.Remotes { 151 if _, has := h.Cids[cid]; has { 152 var md metadata.Metadata 153 switch h.Protocol { 154 case multicodec.TransportBitswap: 155 md = metadata.Default.New(metadata.Bitswap{}) 156 case multicodec.TransportGraphsyncFilecoinv1: 157 md = metadata.Default.New(&metadata.GraphsyncFilecoinV1{PieceCID: cid}) 158 case multicodec.TransportIpfsGatewayHttp: 159 md = metadata.Default.New(&metadata.IpfsGatewayHttp{}) 160 } 161 candidates = append(candidates, types.RetrievalCandidate{MinerPeer: *h.AddrInfo(), RootCid: cid, Metadata: md}) 162 } 163 } 164 return candidates, nil 165 } 166 167 func (mcf *mockCandidateSource) FindCandidates(ctx context.Context, cid cid.Cid, cb func(types.RetrievalCandidate)) error { 168 cand, _ := mcf.findCandidates(ctx, cid) 169 for _, c := range cand { 170 select { 171 case <-ctx.Done(): 172 return ctx.Err() 173 default: 174 } 175 cb(c) 176 } 177 return nil 178 } 179 180 var _ datatransfer.RequestValidator = (*mockDealValidator)(nil) 181 182 type mockDealValidator struct { 183 t *testing.T 184 acceptPull bool 185 } 186 187 func (mdv *mockDealValidator) ValidatePush( 188 channel datatransfer.ChannelID, 189 sender peer.ID, 190 voucher datamodel.Node, 191 baseCid cid.Cid, 192 selector datamodel.Node, 193 ) (datatransfer.ValidationResult, error) { 194 return datatransfer.ValidationResult{Accepted: false}, errors.New("not supported") 195 } 196 197 func (mdv *mockDealValidator) ValidatePull( 198 channel datatransfer.ChannelID, 199 receiver peer.ID, 200 voucher datamodel.Node, 201 baseCid cid.Cid, 202 selector datamodel.Node, 203 ) (datatransfer.ValidationResult, error) { 204 if voucher.Kind() != datamodel.Kind_Map { 205 mdv.t.Logf("rejecting pull, bad voucher (!map)") 206 return datatransfer.ValidationResult{Accepted: false}, nil 207 } 208 pcn, err := voucher.LookupByString("PayloadCID") 209 if err != nil || pcn.Kind() != datamodel.Kind_Link { 210 mdv.t.Logf("rejecting pull, bad voucher PayloadCID") 211 return datatransfer.ValidationResult{Accepted: false}, nil 212 } 213 pcl, err := pcn.AsLink() 214 if err != nil || !baseCid.Equals(pcl.(cidlink.Link).Cid) { 215 mdv.t.Logf("rejecting pull, bad voucher PayloadCID (doesn't match)") 216 return datatransfer.ValidationResult{Accepted: false}, nil 217 } 218 pn, err := voucher.LookupByString("Params") 219 if err != nil || pn.Kind() != datamodel.Kind_Map { 220 mdv.t.Logf("rejecting pull, bad voucher Params") 221 return datatransfer.ValidationResult{Accepted: false}, nil 222 } 223 sn, err := pn.LookupByString("Selector") 224 if err != nil || !ipld.DeepEqual(sn, selector) { 225 mdv.t.Logf("rejecting pull, bad voucher Selector") 226 return datatransfer.ValidationResult{Accepted: false}, nil 227 } 228 return datatransfer.ValidationResult{Accepted: mdv.acceptPull}, nil 229 } 230 231 func (mdv *mockDealValidator) ValidateRestart( 232 channelID datatransfer.ChannelID, 233 channel datatransfer.ChannelState, 234 ) (datatransfer.ValidationResult, error) { 235 return datatransfer.ValidationResult{Accepted: false}, errors.New("not supported") 236 }