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  }