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 }