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

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/libp2p/go-libp2p-core/network"
     9  	"github.com/libp2p/go-libp2p-core/peer"
    10  	protocol "github.com/libp2p/go-libp2p-core/protocol"
    11  	"github.com/qri-io/qri/dsref"
    12  	p2putil "github.com/qri-io/qri/p2p/p2putil"
    13  )
    14  
    15  const (
    16  	// p2pRefResolverTimeout is the length of time we will wait for a
    17  	// RefResolverRequest response before cancelling the context
    18  	// this can potentially be a config option in the future
    19  	p2pRefResolverTimeout = time.Second * 20
    20  	// ResolveRefProtocolID is the protocol on which qri nodes communicate to
    21  	// resolve references
    22  	ResolveRefProtocolID = protocol.ID("/qri/ref/0.1.0")
    23  )
    24  
    25  type p2pRefResolver struct {
    26  	node *QriNode
    27  }
    28  
    29  type resolveRefRes struct {
    30  	ref    *dsref.Ref
    31  	source string
    32  }
    33  
    34  func (rr *p2pRefResolver) ResolveRef(ctx context.Context, ref *dsref.Ref) (string, error) {
    35  	log.Debugf("p2p.ResolveRef ref=%q", ref)
    36  	if rr == nil || rr.node == nil {
    37  		return "", dsref.ErrRefNotFound
    38  	}
    39  	refCp := ref.Copy()
    40  	streamCtx, cancel := context.WithTimeout(ctx, p2pRefResolverTimeout)
    41  	defer cancel()
    42  
    43  	connectedPids := rr.node.ConnectedQriPeerIDs()
    44  	numReqs := len(connectedPids)
    45  	if numReqs == 0 {
    46  		return "", dsref.ErrRefNotFound
    47  	}
    48  
    49  	resCh := make(chan resolveRefRes, numReqs)
    50  	for _, pid := range connectedPids {
    51  		go func(pid peer.ID, reqRef dsref.Ref) {
    52  			source := rr.resolveRefRequest(streamCtx, pid, &reqRef)
    53  			resCh <- resolveRefRes{
    54  				ref:    &reqRef,
    55  				source: source,
    56  			}
    57  		}(pid, refCp.Copy())
    58  	}
    59  
    60  	for {
    61  		select {
    62  		case res := <-resCh:
    63  			numReqs--
    64  			if !res.ref.Complete() && numReqs == 0 {
    65  				return "", dsref.ErrRefNotFound
    66  			}
    67  			if res.ref.Complete() {
    68  				*ref = *res.ref
    69  				return res.source, nil
    70  			}
    71  		case <-streamCtx.Done():
    72  			log.Debug("p2p.ResolveRef context canceled or timed out before resolving ref")
    73  			return "", fmt.Errorf("p2p.ResolveRef context: %w", streamCtx.Err())
    74  		}
    75  	}
    76  }
    77  
    78  func (rr *p2pRefResolver) resolveRefRequest(ctx context.Context, pid peer.ID, ref *dsref.Ref) string {
    79  	var (
    80  		err error
    81  		s   network.Stream
    82  	)
    83  
    84  	defer func() {
    85  		if s != nil {
    86  			// close the stream from this end and wait until the other end has also closed
    87  			// This closes the stream not the underlying connection
    88  			s.Close()
    89  		}
    90  	}()
    91  
    92  	log.Debug("p2p.ResolveRef - sending ref request to ", pid)
    93  	s, err = rr.node.Host().NewStream(ctx, pid, ResolveRefProtocolID)
    94  	if err != nil {
    95  		log.Debugf("p2p.ResolveRef - error opening resolve ref stream to peer %q: %s", pid, err)
    96  		return ""
    97  	}
    98  
    99  	err = sendRef(s, ref)
   100  	if err != nil {
   101  		log.Debugf("p2p.ResolveRef - error sending request ref to %q: %s", pid, err)
   102  		return ""
   103  	}
   104  
   105  	receivedRef, err := receiveRef(s)
   106  	if err != nil {
   107  		log.Debugf("p2p.ResolveRef - error reading ref message from %q: %s", pid, err)
   108  		return ""
   109  	}
   110  	*ref = *receivedRef
   111  	return pid.Pretty()
   112  }
   113  
   114  func sendRef(s network.Stream, ref *dsref.Ref) error {
   115  	ws := p2putil.WrapStream(s)
   116  
   117  	if err := ws.Enc.Encode(&ref); err != nil {
   118  		return fmt.Errorf("error encoding dsref.Ref to wrapped stream: %s", err)
   119  	}
   120  
   121  	if err := ws.W.Flush(); err != nil {
   122  		return fmt.Errorf("error flushing stream: %s", err)
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func receiveRef(s network.Stream) (*dsref.Ref, error) {
   129  	ws := p2putil.WrapStream(s)
   130  	ref := &dsref.Ref{}
   131  	if err := ws.Dec.Decode(ref); err != nil {
   132  		return nil, fmt.Errorf("error decoding dsref.Ref from wrapped stream: %s", err)
   133  	}
   134  	return ref, nil
   135  }
   136  
   137  // NewP2PRefResolver creates a resolver backed by a qri node
   138  func (q *QriNode) NewP2PRefResolver() dsref.Resolver {
   139  	return &p2pRefResolver{node: q}
   140  }
   141  
   142  // ResolveRefHandler is a handler func that belongs on the QriNode
   143  // it handles request made on the `ResolveRefProtocol`
   144  func (q *QriNode) resolveRefHandler(s network.Stream) {
   145  	if q.localResolver == nil {
   146  		log.Errorf("p2p.ResolverRef - qri node has no local resolver, and so cannot handle ref resolution")
   147  		return
   148  	}
   149  	var (
   150  		err error
   151  		ref *dsref.Ref
   152  	)
   153  	ctx, cancel := context.WithTimeout(context.Background(), p2pRefResolverTimeout)
   154  	defer func() {
   155  		if s != nil {
   156  			// close the stream, and wait for the other end of the stream to close as well
   157  			// this won't close the underlying connection
   158  			s.Close()
   159  		}
   160  		cancel()
   161  	}()
   162  
   163  	p := s.Conn().RemotePeer()
   164  	log.Debugf("p2p.resolveRefHandler received a ref request from %s %s", p, s.Conn().RemoteMultiaddr())
   165  
   166  	// get ref from stream
   167  	ref, err = receiveRef(s)
   168  	if err != nil {
   169  		log.Debugf("p2p.resolveRefHandler - error reading ref message from %q: %s", p, err)
   170  		return
   171  	}
   172  
   173  	// try to resolve this ref locally
   174  	_, err = q.localResolver.ResolveRef(ctx, ref)
   175  	if err != nil {
   176  		log.Debugf("p2p.resolveRefHandler - error resolving ref locally: %s", err)
   177  	}
   178  
   179  	log.Debugf("p2p.resolveRefHandler %q sending ref %v to peer %q", q.host.ID(), ref, p)
   180  	err = sendRef(s, ref)
   181  	if err != nil {
   182  		log.Debugf("p2p.ResolveRef - error sending ref to %q: %s", p, err)
   183  		return
   184  	}
   185  }