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 }