github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/p2p/test/p2ptest.go (about)

     1  // Package p2ptest defines utilities for qri peer-2-peer testing
     2  package p2ptest
     3  
     4  import (
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"path/filepath"
     9  	"sync"
    10  
    11  	"github.com/ipfs/go-cid"
    12  	coreiface "github.com/ipfs/interface-go-ipfs-core"
    13  	host "github.com/libp2p/go-libp2p-core/host"
    14  	peer "github.com/libp2p/go-libp2p-core/peer"
    15  	ma "github.com/multiformats/go-multiaddr"
    16  	"github.com/qri-io/dag"
    17  	testkeys "github.com/qri-io/qri/auth/key/test"
    18  	"github.com/qri-io/qri/config"
    19  	"github.com/qri-io/qri/event"
    20  	"github.com/qri-io/qri/profile"
    21  	"github.com/qri-io/qri/repo"
    22  	reporef "github.com/qri-io/qri/repo/ref"
    23  	"github.com/qri-io/qri/repo/test"
    24  )
    25  
    26  // TestablePeerNode is used by tests only. Implemented by QriNode
    27  type TestablePeerNode interface {
    28  	Host() host.Host
    29  	SimpleAddrInfo() peer.AddrInfo
    30  	GoOnline(ctx context.Context) error
    31  }
    32  
    33  // NodeMakerFunc is a function that constructs a Node from a Repo and options.
    34  type NodeMakerFunc func(repo.Repo, *config.P2P, event.Publisher) (TestablePeerNode, error)
    35  
    36  // TestNodeFactory can be used to safetly construct nodes for tests
    37  type TestNodeFactory struct {
    38  	count int
    39  	maker NodeMakerFunc
    40  	pub   event.Publisher
    41  }
    42  
    43  // NewTestNodeFactory returns a new TestNodeFactory
    44  func NewTestNodeFactory(maker NodeMakerFunc) *TestNodeFactory {
    45  	return &TestNodeFactory{count: 0, maker: maker, pub: event.NilBus}
    46  }
    47  
    48  // NewTestNodeFactoryWithBus returns a new TestNodeFactory with non nil buses
    49  func NewTestNodeFactoryWithBus(maker NodeMakerFunc) *TestNodeFactory {
    50  	return &TestNodeFactory{count: 0, maker: maker, pub: event.NewBus(context.Background())}
    51  }
    52  
    53  // New creates a new Node for testing
    54  func (f *TestNodeFactory) New(r repo.Repo) (TestablePeerNode, error) {
    55  	kd := testkeys.GetKeyData(f.count)
    56  	f.count++
    57  	p2pconf := config.DefaultP2P()
    58  	p2pconf.PeerID = kd.EncodedPeerID
    59  	p2pconf.PrivKey = kd.EncodedPrivKey
    60  	return f.maker(r, p2pconf, f.pub)
    61  }
    62  
    63  // NewWithConf creates a new Node for testing using a configuration
    64  func (f *TestNodeFactory) NewWithConf(r repo.Repo, p2pconf *config.P2P) (TestablePeerNode, error) {
    65  	kd := testkeys.GetKeyData(f.count)
    66  	f.count++
    67  	p2pconf.PeerID = kd.EncodedPeerID
    68  	p2pconf.PrivKey = kd.EncodedPrivKey
    69  	return f.maker(r, p2pconf, f.pub)
    70  }
    71  
    72  // NextKeyData gets the KeyData for the next test Node to be constructed
    73  func (f *TestNodeFactory) NextKeyData() *testkeys.KeyData {
    74  	return testkeys.GetKeyData(f.count)
    75  }
    76  
    77  // NewTestNetwork constructs nodes to test p2p functionality.
    78  // each of these peers has no datasets and no peers are connected
    79  func NewTestNetwork(ctx context.Context, f *TestNodeFactory, num int) ([]TestablePeerNode, error) {
    80  	nodes := make([]TestablePeerNode, num)
    81  	for i := 0; i < num; i++ {
    82  		info := f.NextKeyData()
    83  		r, err := test.NewTestRepoFromProfileID(profile.IDFromPeerID(info.PeerID), i, i)
    84  		if err != nil {
    85  			return nil, fmt.Errorf("error creating test repo: %s", err.Error())
    86  		}
    87  		node, err := NewAvailableTestNode(ctx, r, f)
    88  		if err != nil {
    89  			return nil, err
    90  		}
    91  		nodes[i] = node
    92  	}
    93  	return nodes, nil
    94  }
    95  
    96  // NewNodeWithBus constructs the next node in the factory, but with
    97  // the given bus, helpful for tests where you need to subscribe to specific
    98  // events in order to coordiate timing
    99  func NewNodeWithBus(ctx context.Context, f *TestNodeFactory, bus event.Publisher) (TestablePeerNode, error) {
   100  	prevBus := f.pub
   101  	defer func() {
   102  		f.pub = prevBus
   103  	}()
   104  	kd := f.NextKeyData()
   105  	r, err := test.NewTestRepoFromProfileID(profile.IDFromPeerID(kd.PeerID), 0, 0)
   106  	if err != nil {
   107  		return nil, fmt.Errorf("error creating test repo: %s", err.Error())
   108  	}
   109  
   110  	f.pub = bus
   111  	node, err := NewAvailableTestNode(ctx, r, f)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	return node, nil
   116  }
   117  
   118  // NewTestDirNetwork constructs nodes from the testdata directory, for p2p testing
   119  // Peers are pulled from the "testdata" directory, and come pre-populated with datasets
   120  // no peers are connected.
   121  func NewTestDirNetwork(ctx context.Context, f *TestNodeFactory) ([]TestablePeerNode, error) {
   122  	dirs, err := ioutil.ReadDir("testdata")
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	nodes := []TestablePeerNode{}
   128  	for _, dir := range dirs {
   129  		if dir.IsDir() {
   130  
   131  			repo, _, err := test.NewMemRepoFromDir(filepath.Join("testdata", dir.Name()))
   132  			if err != nil {
   133  				return nil, err
   134  			}
   135  
   136  			node, err := NewAvailableTestNode(ctx, repo, f)
   137  			if err != nil {
   138  				return nil, err
   139  			}
   140  			nodes = append(nodes, node)
   141  		}
   142  	}
   143  	return nodes, nil
   144  }
   145  
   146  // NewAvailableTestNode constructs a test node that is hooked up and ready to Connect
   147  func NewAvailableTestNode(ctx context.Context, r repo.Repo, f *TestNodeFactory) (TestablePeerNode, error) {
   148  	info := f.NextKeyData()
   149  	addr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0")
   150  	p2pconf := config.DefaultP2P()
   151  	p2pconf.Addrs = []ma.Multiaddr{addr}
   152  	p2pconf.QriBootstrapAddrs = []string{}
   153  	node, err := f.NewWithConf(r, p2pconf)
   154  	if err != nil {
   155  		return nil, fmt.Errorf("error creating test node: %s", err.Error())
   156  	}
   157  	if err := node.GoOnline(ctx); err != nil {
   158  		return nil, fmt.Errorf("errror connecting: %s", err.Error())
   159  	}
   160  	node.Host().Peerstore().AddPubKey(info.PeerID, info.PrivKey.GetPublic())
   161  	node.Host().Peerstore().AddPrivKey(info.PeerID, info.PrivKey)
   162  	return node, err
   163  }
   164  
   165  // ConnectNodes creates a basic connection between the nodes. This connection
   166  // mirrors the connection that would normally occur between two p2p nodes.
   167  // The Host.Connect function adds the addresses into the peerstore and dials the
   168  // remote peer.
   169  // Take a look at https://github.com/libp2p/go-libp2p-core/host/blob/623ffaa4ef2b8dad77933159d0848a393a91c41e/host.go#L36
   170  // for more info
   171  // Connect should always:
   172  // - add a connections to the peer
   173  // - add the addrs of the peer to the peerstore
   174  // - add a tag for each peer in the connmanager
   175  func ConnectNodes(ctx context.Context, nodes []TestablePeerNode) error {
   176  	var wg sync.WaitGroup
   177  	connect := func(n TestablePeerNode, pinfo peer.AddrInfo) error {
   178  		if err := n.Host().Connect(ctx, pinfo); err != nil {
   179  			return fmt.Errorf("error connecting nodes: %s", err)
   180  		}
   181  		wg.Done()
   182  		return nil
   183  	}
   184  
   185  	for i, s1 := range nodes {
   186  		for _, s2 := range nodes[i+1:] {
   187  			wg.Add(1)
   188  			if err := connect(s1, s2.SimpleAddrInfo()); err != nil {
   189  				return err
   190  			}
   191  		}
   192  	}
   193  	wg.Wait()
   194  	// wait for
   195  	return nil
   196  }
   197  
   198  // GetSomeBlocks returns a list of num ids for blocks that are in the referenced dataset.
   199  func GetSomeBlocks(capi coreiface.CoreAPI, ref reporef.DatasetRef, num int) []string {
   200  	result := []string{}
   201  
   202  	ctx := context.Background()
   203  
   204  	ng := dag.NewNodeGetter(capi.Dag())
   205  
   206  	id, err := cid.Parse(ref.Path)
   207  	if err != nil {
   208  		panic(err)
   209  	}
   210  
   211  	elem, err := ng.Get(ctx, id)
   212  	if err != nil {
   213  		panic(err)
   214  	}
   215  
   216  	for _, link := range elem.Links() {
   217  		result = append(result, link.Cid.String())
   218  		if len(result) >= num {
   219  			break
   220  		}
   221  	}
   222  
   223  	return result
   224  }