github.com/celestiaorg/celestia-node@v0.15.0-beta.1/share/p2p/shrexnd/client.go (about) 1 package shrexnd 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 "time" 10 11 "github.com/libp2p/go-libp2p/core/host" 12 "github.com/libp2p/go-libp2p/core/network" 13 "github.com/libp2p/go-libp2p/core/peer" 14 "github.com/libp2p/go-libp2p/core/protocol" 15 16 "github.com/celestiaorg/go-libp2p-messenger/serde" 17 "github.com/celestiaorg/nmt" 18 19 "github.com/celestiaorg/celestia-node/share" 20 "github.com/celestiaorg/celestia-node/share/p2p" 21 pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" 22 ) 23 24 // Client implements client side of shrex/nd protocol to obtain namespaced shares data from remote 25 // peers. 26 type Client struct { 27 params *Parameters 28 protocolID protocol.ID 29 30 host host.Host 31 metrics *p2p.Metrics 32 } 33 34 // NewClient creates a new shrEx/nd client 35 func NewClient(params *Parameters, host host.Host) (*Client, error) { 36 if err := params.Validate(); err != nil { 37 return nil, fmt.Errorf("shrex-nd: client creation failed: %w", err) 38 } 39 40 return &Client{ 41 host: host, 42 protocolID: p2p.ProtocolID(params.NetworkID(), protocolString), 43 params: params, 44 }, nil 45 } 46 47 // RequestND requests namespaced data from the given peer. 48 // Returns NamespacedShares with unverified inclusion proofs against the share.Root. 49 func (c *Client) RequestND( 50 ctx context.Context, 51 root *share.Root, 52 namespace share.Namespace, 53 peer peer.ID, 54 ) (share.NamespacedShares, error) { 55 if err := namespace.ValidateForData(); err != nil { 56 return nil, err 57 } 58 59 shares, err := c.doRequest(ctx, root, namespace, peer) 60 if err == nil { 61 return shares, nil 62 } 63 if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { 64 c.metrics.ObserveRequests(ctx, 1, p2p.StatusTimeout) 65 return nil, err 66 } 67 // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not 68 // unwrap to a ctx err 69 var ne net.Error 70 if errors.As(err, &ne) && ne.Timeout() { 71 if deadline, _ := ctx.Deadline(); deadline.Before(time.Now()) { 72 c.metrics.ObserveRequests(ctx, 1, p2p.StatusTimeout) 73 return nil, context.DeadlineExceeded 74 } 75 } 76 if err != p2p.ErrNotFound && err != p2p.ErrRateLimited { 77 log.Warnw("client-nd: peer returned err", "err", err) 78 } 79 return nil, err 80 } 81 82 func (c *Client) doRequest( 83 ctx context.Context, 84 root *share.Root, 85 namespace share.Namespace, 86 peerID peer.ID, 87 ) (share.NamespacedShares, error) { 88 stream, err := c.host.NewStream(ctx, peerID, c.protocolID) 89 if err != nil { 90 return nil, err 91 } 92 defer stream.Close() 93 94 c.setStreamDeadlines(ctx, stream) 95 96 req := &pb.GetSharesByNamespaceRequest{ 97 RootHash: root.Hash(), 98 Namespace: namespace, 99 } 100 101 _, err = serde.Write(stream, req) 102 if err != nil { 103 c.metrics.ObserveRequests(ctx, 1, p2p.StatusSendReqErr) 104 stream.Reset() //nolint:errcheck 105 return nil, fmt.Errorf("client-nd: writing request: %w", err) 106 } 107 108 err = stream.CloseWrite() 109 if err != nil { 110 log.Debugw("client-nd: closing write side of the stream", "err", err) 111 } 112 113 if err := c.readStatus(ctx, stream); err != nil { 114 return nil, err 115 } 116 return c.readNamespacedShares(ctx, stream) 117 } 118 119 func (c *Client) readStatus(ctx context.Context, stream network.Stream) error { 120 var resp pb.GetSharesByNamespaceStatusResponse 121 _, err := serde.Read(stream, &resp) 122 if err != nil { 123 // server is overloaded and closed the stream 124 if errors.Is(err, io.EOF) { 125 c.metrics.ObserveRequests(ctx, 1, p2p.StatusRateLimited) 126 return p2p.ErrRateLimited 127 } 128 c.metrics.ObserveRequests(ctx, 1, p2p.StatusReadRespErr) 129 stream.Reset() //nolint:errcheck 130 return fmt.Errorf("client-nd: reading status response: %w", err) 131 } 132 133 return c.convertStatusToErr(ctx, resp.Status) 134 } 135 136 // readNamespacedShares converts proto Rows to share.NamespacedShares 137 func (c *Client) readNamespacedShares( 138 ctx context.Context, 139 stream network.Stream, 140 ) (share.NamespacedShares, error) { 141 var shares share.NamespacedShares 142 for { 143 var row pb.NamespaceRowResponse 144 _, err := serde.Read(stream, &row) 145 if err != nil { 146 if errors.Is(err, io.EOF) { 147 // all data is received and steam is closed by server 148 return shares, nil 149 } 150 c.metrics.ObserveRequests(ctx, 1, p2p.StatusReadRespErr) 151 return nil, err 152 } 153 var proof nmt.Proof 154 if row.Proof != nil { 155 if len(row.Shares) != 0 { 156 proof = nmt.NewInclusionProof( 157 int(row.Proof.Start), 158 int(row.Proof.End), 159 row.Proof.Nodes, 160 row.Proof.IsMaxNamespaceIgnored, 161 ) 162 } else { 163 proof = nmt.NewAbsenceProof( 164 int(row.Proof.Start), 165 int(row.Proof.End), 166 row.Proof.Nodes, 167 row.Proof.LeafHash, 168 row.Proof.IsMaxNamespaceIgnored, 169 ) 170 } 171 } 172 shares = append(shares, share.NamespacedRow{ 173 Shares: row.Shares, 174 Proof: &proof, 175 }) 176 } 177 } 178 179 func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) { 180 // set read/write deadline to use context deadline if it exists 181 deadline, ok := ctx.Deadline() 182 if ok { 183 err := stream.SetDeadline(deadline) 184 if err == nil { 185 return 186 } 187 log.Debugw("client-nd: set stream deadline", "err", err) 188 } 189 190 // if deadline not set, client read deadline defaults to server write deadline 191 if c.params.ServerWriteTimeout != 0 { 192 err := stream.SetReadDeadline(time.Now().Add(c.params.ServerWriteTimeout)) 193 if err != nil { 194 log.Debugw("client-nd: set read deadline", "err", err) 195 } 196 } 197 198 // if deadline not set, client write deadline defaults to server read deadline 199 if c.params.ServerReadTimeout != 0 { 200 err := stream.SetWriteDeadline(time.Now().Add(c.params.ServerReadTimeout)) 201 if err != nil { 202 log.Debugw("client-nd: set write deadline", "err", err) 203 } 204 } 205 } 206 207 func (c *Client) convertStatusToErr(ctx context.Context, status pb.StatusCode) error { 208 switch status { 209 case pb.StatusCode_OK: 210 c.metrics.ObserveRequests(ctx, 1, p2p.StatusSuccess) 211 return nil 212 case pb.StatusCode_NOT_FOUND: 213 c.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) 214 return p2p.ErrNotFound 215 case pb.StatusCode_INVALID: 216 log.Warn("client-nd: invalid request") 217 fallthrough 218 case pb.StatusCode_INTERNAL: 219 fallthrough 220 default: 221 return p2p.ErrInvalidResponse 222 } 223 }