github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/rpc/apimiddleware/custom_handlers.go (about)

     1  package apimiddleware
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/prysmaticlabs/prysm/beacon-chain/rpc/eth/v1/events"
    15  	"github.com/prysmaticlabs/prysm/shared/gateway"
    16  	"github.com/prysmaticlabs/prysm/shared/grpcutils"
    17  	"github.com/r3labs/sse"
    18  )
    19  
    20  type sszConfig struct {
    21  	sszPath      string
    22  	fileName     string
    23  	responseJson sszResponseJson
    24  }
    25  
    26  func handleGetBeaconStateSSZ(m *gateway.ApiProxyMiddleware, endpoint gateway.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) {
    27  	config := sszConfig{
    28  		sszPath:      "/eth/v1/debug/beacon/states/{state_id}/ssz",
    29  		fileName:     "beacon_state.ssz",
    30  		responseJson: &beaconStateSSZResponseJson{},
    31  	}
    32  	return handleGetSSZ(m, endpoint, w, req, config)
    33  }
    34  
    35  func handleGetBeaconBlockSSZ(m *gateway.ApiProxyMiddleware, endpoint gateway.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) {
    36  	config := sszConfig{
    37  		sszPath:      "/eth/v1/beacon/blocks/{block_id}/ssz",
    38  		fileName:     "beacon_block.ssz",
    39  		responseJson: &blockSSZResponseJson{},
    40  	}
    41  	return handleGetSSZ(m, endpoint, w, req, config)
    42  }
    43  
    44  func handleGetSSZ(
    45  	m *gateway.ApiProxyMiddleware,
    46  	endpoint gateway.Endpoint,
    47  	w http.ResponseWriter,
    48  	req *http.Request,
    49  	config sszConfig,
    50  ) (handled bool) {
    51  	if !sszRequested(req) {
    52  		return false
    53  	}
    54  
    55  	if errJson := prepareSSZRequestForProxying(m, endpoint, req, config.sszPath); errJson != nil {
    56  		gateway.WriteError(w, errJson, nil)
    57  		return true
    58  	}
    59  	grpcResponse, errJson := gateway.ProxyRequest(req)
    60  	if errJson != nil {
    61  		gateway.WriteError(w, errJson, nil)
    62  		return true
    63  	}
    64  	grpcResponseBody, errJson := gateway.ReadGrpcResponseBody(grpcResponse.Body)
    65  	if errJson != nil {
    66  		gateway.WriteError(w, errJson, nil)
    67  		return true
    68  	}
    69  	if errJson := gateway.DeserializeGrpcResponseBodyIntoErrorJson(endpoint.Err, grpcResponseBody); errJson != nil {
    70  		gateway.WriteError(w, errJson, nil)
    71  		return true
    72  	}
    73  	if endpoint.Err.Msg() != "" {
    74  		gateway.HandleGrpcResponseError(endpoint.Err, grpcResponse, w)
    75  		return true
    76  	}
    77  	if errJson := gateway.DeserializeGrpcResponseBodyIntoContainer(grpcResponseBody, config.responseJson); errJson != nil {
    78  		gateway.WriteError(w, errJson, nil)
    79  		return true
    80  	}
    81  	responseSsz, errJson := serializeMiddlewareResponseIntoSSZ(config.responseJson.SSZData())
    82  	if errJson != nil {
    83  		gateway.WriteError(w, errJson, nil)
    84  		return true
    85  	}
    86  	if errJson := writeSSZResponseHeaderAndBody(grpcResponse, w, responseSsz, config.fileName); errJson != nil {
    87  		gateway.WriteError(w, errJson, nil)
    88  		return true
    89  	}
    90  	if errJson := gateway.Cleanup(grpcResponse.Body); errJson != nil {
    91  		gateway.WriteError(w, errJson, nil)
    92  		return true
    93  	}
    94  
    95  	return true
    96  }
    97  
    98  func sszRequested(req *http.Request) bool {
    99  	accept, ok := req.Header["Accept"]
   100  	if !ok {
   101  		return false
   102  	}
   103  	for _, v := range accept {
   104  		if v == "application/octet-stream" {
   105  			return true
   106  		}
   107  	}
   108  	return false
   109  }
   110  
   111  func prepareSSZRequestForProxying(m *gateway.ApiProxyMiddleware, endpoint gateway.Endpoint, req *http.Request, sszPath string) gateway.ErrorJson {
   112  	req.URL.Scheme = "http"
   113  	req.URL.Host = m.GatewayAddress
   114  	req.RequestURI = ""
   115  	req.URL.Path = sszPath
   116  	return gateway.HandleURLParameters(endpoint.Path, req, []string{})
   117  }
   118  
   119  func serializeMiddlewareResponseIntoSSZ(data string) (sszResponse []byte, errJson gateway.ErrorJson) {
   120  	// Serialize the SSZ part of the deserialized value.
   121  	b, err := base64.StdEncoding.DecodeString(data)
   122  	if err != nil {
   123  		return nil, gateway.InternalServerErrorWithMessage(err, "could not decode response body into base64")
   124  	}
   125  	return b, nil
   126  }
   127  
   128  func writeSSZResponseHeaderAndBody(grpcResp *http.Response, w http.ResponseWriter, responseSsz []byte, fileName string) gateway.ErrorJson {
   129  	var statusCodeHeader string
   130  	for h, vs := range grpcResp.Header {
   131  		// We don't want to expose any gRPC metadata in the HTTP response, so we skip forwarding metadata headers.
   132  		if strings.HasPrefix(h, "Grpc-Metadata") {
   133  			if h == "Grpc-Metadata-"+grpcutils.HttpCodeMetadataKey {
   134  				statusCodeHeader = vs[0]
   135  			}
   136  		} else {
   137  			for _, v := range vs {
   138  				w.Header().Set(h, v)
   139  			}
   140  		}
   141  	}
   142  	if statusCodeHeader != "" {
   143  		code, err := strconv.Atoi(statusCodeHeader)
   144  		if err != nil {
   145  			return gateway.InternalServerErrorWithMessage(err, "could not parse status code")
   146  		}
   147  		w.WriteHeader(code)
   148  	} else {
   149  		w.WriteHeader(grpcResp.StatusCode)
   150  	}
   151  	w.Header().Set("Content-Length", strconv.Itoa(len(responseSsz)))
   152  	w.Header().Set("Content-Type", "application/octet-stream")
   153  	w.Header().Set("Content-Disposition", "attachment; filename="+fileName)
   154  	w.WriteHeader(grpcResp.StatusCode)
   155  	if _, err := io.Copy(w, ioutil.NopCloser(bytes.NewReader(responseSsz))); err != nil {
   156  		return gateway.InternalServerErrorWithMessage(err, "could not write response message")
   157  	}
   158  	return nil
   159  }
   160  
   161  func handleEvents(m *gateway.ApiProxyMiddleware, _ gateway.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) {
   162  	sseClient := sse.NewClient("http://" + m.GatewayAddress + req.URL.RequestURI())
   163  	eventChan := make(chan *sse.Event)
   164  
   165  	// We use grpc-gateway as the server side of events, not the sse library.
   166  	// Because of this subscribing to streams doesn't work as intended, resulting in each event being handled by all subscriptions.
   167  	// To handle events properly, we subscribe just once using a placeholder value ('events') and handle all topics inside this subscription.
   168  	if err := sseClient.SubscribeChan("events", eventChan); err != nil {
   169  		gateway.WriteError(w, gateway.InternalServerError(err), nil)
   170  		sseClient.Unsubscribe(eventChan)
   171  		return
   172  	}
   173  
   174  	errJson := receiveEvents(eventChan, w, req)
   175  	if errJson != nil {
   176  		gateway.WriteError(w, errJson, nil)
   177  	}
   178  
   179  	sseClient.Unsubscribe(eventChan)
   180  	return true
   181  }
   182  
   183  func receiveEvents(eventChan <-chan *sse.Event, w http.ResponseWriter, req *http.Request) gateway.ErrorJson {
   184  	for {
   185  		select {
   186  		case msg := <-eventChan:
   187  			var data interface{}
   188  
   189  			switch strings.TrimSpace(string(msg.Event)) {
   190  			case events.HeadTopic:
   191  				data = &eventHeadJson{}
   192  			case events.BlockTopic:
   193  				data = &receivedBlockDataJson{}
   194  			case events.AttestationTopic:
   195  				data = &attestationJson{}
   196  
   197  				// Data received in the event does not fit the expected event stream output.
   198  				// We extract the underlying attestation from event data
   199  				// and assign the attestation back to event data for further processing.
   200  				eventData := &aggregatedAttReceivedDataJson{}
   201  				if err := json.Unmarshal(msg.Data, eventData); err != nil {
   202  					return gateway.InternalServerError(err)
   203  				}
   204  				attData, err := json.Marshal(eventData.Aggregate)
   205  				if err != nil {
   206  					return gateway.InternalServerError(err)
   207  				}
   208  				msg.Data = attData
   209  			case events.VoluntaryExitTopic:
   210  				data = &signedVoluntaryExitJson{}
   211  			case events.FinalizedCheckpointTopic:
   212  				data = &eventFinalizedCheckpointJson{}
   213  			case events.ChainReorgTopic:
   214  				data = &eventChainReorgJson{}
   215  			case "error":
   216  				data = &eventErrorJson{}
   217  			default:
   218  				return &gateway.DefaultErrorJson{
   219  					Message: fmt.Sprintf("Event type '%s' not supported", strings.TrimSpace(string(msg.Event))),
   220  					Code:    http.StatusInternalServerError,
   221  				}
   222  			}
   223  
   224  			if errJson := writeEvent(msg, w, data); errJson != nil {
   225  				return errJson
   226  			}
   227  			if errJson := flushEvent(w); errJson != nil {
   228  				return errJson
   229  			}
   230  		case <-req.Context().Done():
   231  			return nil
   232  		}
   233  	}
   234  }
   235  
   236  func writeEvent(msg *sse.Event, w http.ResponseWriter, data interface{}) gateway.ErrorJson {
   237  	if err := json.Unmarshal(msg.Data, data); err != nil {
   238  		return gateway.InternalServerError(err)
   239  	}
   240  	if errJson := gateway.ProcessMiddlewareResponseFields(data); errJson != nil {
   241  		return errJson
   242  	}
   243  	dataJson, errJson := gateway.SerializeMiddlewareResponseIntoJson(data)
   244  	if errJson != nil {
   245  		return errJson
   246  	}
   247  
   248  	w.Header().Set("Content-Type", "text/event-stream")
   249  
   250  	if _, err := w.Write([]byte("event: ")); err != nil {
   251  		return gateway.InternalServerError(err)
   252  	}
   253  	if _, err := w.Write(msg.Event); err != nil {
   254  		return gateway.InternalServerError(err)
   255  	}
   256  	if _, err := w.Write([]byte("\ndata: ")); err != nil {
   257  		return gateway.InternalServerError(err)
   258  	}
   259  	if _, err := w.Write(dataJson); err != nil {
   260  		return gateway.InternalServerError(err)
   261  	}
   262  	if _, err := w.Write([]byte("\n\n")); err != nil {
   263  		return gateway.InternalServerError(err)
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  func flushEvent(w http.ResponseWriter) gateway.ErrorJson {
   270  	flusher, ok := w.(http.Flusher)
   271  	if !ok {
   272  		return &gateway.DefaultErrorJson{Message: fmt.Sprintf("Flush not supported in %T", w), Code: http.StatusInternalServerError}
   273  	}
   274  	flusher.Flush()
   275  	return nil
   276  }