code.vegaprotocol.io/vega@v0.79.0/core/blockchain/nullchain/server.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package nullchain
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"net"
    25  	"net/http"
    26  	"strconv"
    27  	"time"
    28  
    29  	"code.vegaprotocol.io/vega/logging"
    30  )
    31  
    32  var ErrInvalidRequest = errors.New("invalid request")
    33  
    34  const (
    35  	NullChainStatusReady     = "chain-ready"
    36  	NullChainStatusReplaying = "chain-replaying"
    37  )
    38  
    39  func (n *NullBlockchain) Stop() error {
    40  	if n.replayer != nil {
    41  		if err := n.replayer.Stop(); err != nil {
    42  			n.log.Error("failed to stop nullchain replayer")
    43  		}
    44  	}
    45  
    46  	if n.srv == nil {
    47  		return nil
    48  	}
    49  
    50  	n.log.Info("Stopping nullchain server")
    51  	if err := n.srv.Shutdown(context.Background()); err != nil {
    52  		n.log.Error("failed to shutdown")
    53  	}
    54  
    55  	return nil
    56  }
    57  
    58  func (n *NullBlockchain) Start() error {
    59  	// the nullblockchain needs to start after the grpc API have started, so we pretend to start here
    60  	return nil
    61  }
    62  
    63  func (n *NullBlockchain) StartServer() error {
    64  	n.srv = &http.Server{Addr: net.JoinHostPort(n.cfg.IP, strconv.Itoa(n.cfg.Port))}
    65  	http.HandleFunc("/api/v1/forwardtime", n.handleForwardTime)
    66  	http.HandleFunc("/api/v1/status", n.status)
    67  
    68  	n.log.Info("starting time-forwarding server", logging.String("addr", n.srv.Addr))
    69  	go n.srv.ListenAndServe()
    70  
    71  	n.log.Info("starting blockchain")
    72  	if err := n.StartChain(); err != nil {
    73  		return err
    74  	}
    75  
    76  	return nil
    77  }
    78  
    79  // RequestToDuration should receive either be a parsable duration or a RFC3339 datetime.
    80  func RequestToDuration(req string, now time.Time) (time.Duration, error) {
    81  	d, err := time.ParseDuration(req)
    82  	if err != nil {
    83  		newTime, err := time.Parse(time.RFC3339, req)
    84  		if err != nil {
    85  			return 0, fmt.Errorf("%w: time is not a duration or RFC3339 datetime", ErrInvalidRequest)
    86  		}
    87  
    88  		// Convert to a duration by subtracting the current frozen time of the nullchain
    89  		d = newTime.Sub(now)
    90  	}
    91  
    92  	if d < 0 {
    93  		return 0, fmt.Errorf("%w: cannot step backwards in time", ErrInvalidRequest)
    94  	}
    95  
    96  	return d, nil
    97  }
    98  
    99  // handleForwardTime processes the incoming request to shuffle forward in time converting it into a valid
   100  // duration from `now` and pushing it into the nullchain to do its thing.
   101  func (n *NullBlockchain) handleForwardTime(w http.ResponseWriter, r *http.Request) {
   102  	defer r.Body.Close()
   103  
   104  	body, err := io.ReadAll(r.Body)
   105  	if err != nil {
   106  		http.Error(w, "unexpected request body", http.StatusBadRequest)
   107  		return
   108  	}
   109  	req := struct {
   110  		Forward string `json:"forward"`
   111  	}{}
   112  
   113  	if err := json.Unmarshal(body, &req); err != nil {
   114  		http.Error(w, "unexpected request body", http.StatusBadRequest)
   115  		return
   116  	}
   117  
   118  	d, err := RequestToDuration(req.Forward, n.now)
   119  	if err != nil {
   120  		http.Error(w, err.Error(), http.StatusBadRequest)
   121  	}
   122  
   123  	if n.replaying.Load() {
   124  		http.Error(w, ErrChainReplaying.Error(), http.StatusServiceUnavailable)
   125  	}
   126  
   127  	// we need to call ForwardTime in a different routine so that if it panics it stops the node instead of just being
   128  	// caught in the http-handler recover. But awkwardly it seems like the vega-sim relies on the http-request not
   129  	// returning until the time-forward has finished, so we need to preserve that for now.
   130  	done := make(chan struct{})
   131  	go func() {
   132  		n.ForwardTime(d)
   133  		done <- struct{}{}
   134  	}()
   135  	<-done
   136  }
   137  
   138  // status returns the status of the nullchain, whether it is replaying or whether its ready to go.
   139  func (n *NullBlockchain) status(w http.ResponseWriter, r *http.Request) {
   140  	w.Header().Set("Content-Type", "application/json")
   141  	w.WriteHeader(http.StatusOK)
   142  
   143  	resp := struct {
   144  		Status string `json:"status"`
   145  	}{
   146  		Status: NullChainStatusReady,
   147  	}
   148  
   149  	if n.replaying.Load() {
   150  		resp.Status = NullChainStatusReplaying
   151  	}
   152  
   153  	buf, err := json.Marshal(resp)
   154  	if err != nil {
   155  		http.Error(w, err.Error(), http.StatusInternalServerError)
   156  		return
   157  	}
   158  
   159  	_, err = w.Write(buf)
   160  	if err != nil {
   161  		http.Error(w, err.Error(), http.StatusInternalServerError)
   162  		return
   163  	}
   164  }