mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
315 lines
7.7 KiB
Go
315 lines
7.7 KiB
Go
package simulations
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p"
|
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
|
"github.com/julienschmidt/httprouter"
|
|
"github.com/pborman/uuid"
|
|
)
|
|
|
|
type ServerConfig struct {
|
|
Adapter adapters.NodeAdapter
|
|
Mocker func(*Network)
|
|
}
|
|
|
|
type Server struct {
|
|
ServerConfig
|
|
|
|
router *httprouter.Router
|
|
networks map[string]*Network
|
|
mtx sync.Mutex
|
|
}
|
|
|
|
func NewServer(config *ServerConfig) *Server {
|
|
if config.Adapter == nil {
|
|
panic("Adapter not set")
|
|
}
|
|
|
|
s := &Server{
|
|
ServerConfig: *config,
|
|
router: httprouter.New(),
|
|
networks: make(map[string]*Network),
|
|
}
|
|
|
|
s.POST("/networks", s.CreateNetwork)
|
|
s.GET("/networks", s.GetNetworks)
|
|
s.GET("/networks/:netid", s.GetNetwork)
|
|
s.POST("/networks/:netid/mock", s.StartMocker)
|
|
s.POST("/networks/:netid/nodes", s.CreateNode)
|
|
s.GET("/networks/:netid/nodes", s.GetNodes)
|
|
s.GET("/networks/:netid/nodes/:nodeid", s.GetNode)
|
|
s.POST("/networks/:netid/nodes/:nodeid/start", s.StartNode)
|
|
s.POST("/networks/:netid/nodes/:nodeid/stop", s.StopNode)
|
|
s.POST("/networks/:netid/nodes/:nodeid/conn/:peerid", s.ConnectNode)
|
|
s.DELETE("/networks/:netid/nodes/:nodeid/conn/:peerid", s.DisconnectNode)
|
|
|
|
return s
|
|
}
|
|
|
|
func (s *Server) CreateNetwork(w http.ResponseWriter, req *http.Request) {
|
|
config := &NetworkConfig{}
|
|
if err := json.NewDecoder(req.Body).Decode(config); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if config.Id == "" {
|
|
config.Id = uuid.NewRandom().String()
|
|
}
|
|
|
|
err := func() error {
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
if _, exists := s.networks[config.Id]; exists {
|
|
return fmt.Errorf("network exists: %s", config.Id)
|
|
}
|
|
network := NewNetwork(s.Adapter, config)
|
|
s.networks[config.Id] = network
|
|
return nil
|
|
}()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
s.JSON(w, http.StatusCreated, config)
|
|
}
|
|
|
|
func (s *Server) GetNetworks(w http.ResponseWriter, req *http.Request) {
|
|
s.mtx.Lock()
|
|
networks := make([]NetworkConfig, 0, len(s.networks))
|
|
for _, network := range s.networks {
|
|
config := network.Config()
|
|
networks = append(networks, *config)
|
|
}
|
|
s.mtx.Unlock()
|
|
|
|
s.JSON(w, http.StatusOK, networks)
|
|
}
|
|
|
|
func (s *Server) GetNetwork(w http.ResponseWriter, req *http.Request) {
|
|
network := req.Context().Value("network").(*Network)
|
|
|
|
if req.Header.Get("Accept") == "text/event-stream" {
|
|
s.streamNetworkEvents(network, w)
|
|
return
|
|
}
|
|
|
|
s.JSON(w, http.StatusOK, network.Config())
|
|
}
|
|
|
|
func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) {
|
|
network := req.Context().Value("network").(*Network)
|
|
|
|
if s.Mocker == nil {
|
|
http.Error(w, "mocker not configured", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
go s.Mocker(network)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func (s *Server) streamNetworkEvents(network *Network, w http.ResponseWriter) {
|
|
events := make(chan *Event)
|
|
sub := network.events.Subscribe(events)
|
|
defer sub.Unsubscribe()
|
|
|
|
// stop the stream if the client goes away
|
|
var clientGone <-chan bool
|
|
if cn, ok := w.(http.CloseNotifier); ok {
|
|
clientGone = cn.CloseNotify()
|
|
}
|
|
|
|
// write writes the given event and data to the stream like:
|
|
//
|
|
// event: <event>
|
|
// data: <data>
|
|
//
|
|
write := func(event, data string) {
|
|
fmt.Fprintf(w, "event: %s\n", event)
|
|
fmt.Fprintf(w, "data: %s\n\n", data)
|
|
if fw, ok := w.(http.Flusher); ok {
|
|
fw.Flush()
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
|
|
w.WriteHeader(http.StatusOK)
|
|
if fw, ok := w.(http.Flusher); ok {
|
|
fw.Flush()
|
|
}
|
|
for {
|
|
select {
|
|
case event := <-events:
|
|
data, err := json.Marshal(event)
|
|
if err != nil {
|
|
write("error", err.Error())
|
|
return
|
|
}
|
|
write("network", string(data))
|
|
case <-clientGone:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) CreateNode(w http.ResponseWriter, req *http.Request) {
|
|
network := req.Context().Value("network").(*Network)
|
|
|
|
config, err := network.NewNode()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.JSON(w, http.StatusCreated, network.GetNode(config.Id))
|
|
}
|
|
|
|
func (s *Server) GetNodes(w http.ResponseWriter, req *http.Request) {
|
|
network := req.Context().Value("network").(*Network)
|
|
|
|
nodes := network.GetNodes()
|
|
|
|
infos := make([]*p2p.NodeInfo, len(nodes))
|
|
for i, node := range nodes {
|
|
infos[i] = node.NodeInfo()
|
|
}
|
|
|
|
s.JSON(w, http.StatusOK, infos)
|
|
}
|
|
|
|
func (s *Server) GetNode(w http.ResponseWriter, req *http.Request) {
|
|
node := req.Context().Value("node").(*Node)
|
|
|
|
s.JSON(w, http.StatusOK, node.NodeInfo())
|
|
}
|
|
|
|
func (s *Server) StartNode(w http.ResponseWriter, req *http.Request) {
|
|
network := req.Context().Value("network").(*Network)
|
|
node := req.Context().Value("node").(*Node)
|
|
|
|
if err := network.Start(node.ID()); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.JSON(w, http.StatusOK, node.NodeInfo())
|
|
}
|
|
|
|
func (s *Server) StopNode(w http.ResponseWriter, req *http.Request) {
|
|
network := req.Context().Value("network").(*Network)
|
|
node := req.Context().Value("node").(*Node)
|
|
|
|
if err := network.Stop(node.ID()); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.JSON(w, http.StatusOK, node.NodeInfo())
|
|
}
|
|
|
|
func (s *Server) ConnectNode(w http.ResponseWriter, req *http.Request) {
|
|
network := req.Context().Value("network").(*Network)
|
|
node := req.Context().Value("node").(*Node)
|
|
peer := req.Context().Value("peer").(*Node)
|
|
|
|
if err := network.Connect(node.ID(), peer.ID()); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.JSON(w, http.StatusOK, node.NodeInfo())
|
|
}
|
|
|
|
func (s *Server) DisconnectNode(w http.ResponseWriter, req *http.Request) {
|
|
network := req.Context().Value("network").(*Network)
|
|
node := req.Context().Value("node").(*Node)
|
|
peer := req.Context().Value("peer").(*Node)
|
|
|
|
if err := network.Disconnect(node.ID(), peer.ID()); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.JSON(w, http.StatusOK, node.NodeInfo())
|
|
}
|
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
s.router.ServeHTTP(w, req)
|
|
}
|
|
|
|
func (s *Server) GET(path string, handle http.HandlerFunc) {
|
|
s.router.GET(path, s.wrapHandler(handle))
|
|
}
|
|
|
|
func (s *Server) POST(path string, handle http.HandlerFunc) {
|
|
s.router.POST(path, s.wrapHandler(handle))
|
|
}
|
|
|
|
func (s *Server) DELETE(path string, handle http.HandlerFunc) {
|
|
s.router.DELETE(path, s.wrapHandler(handle))
|
|
}
|
|
|
|
func (s *Server) JSON(w http.ResponseWriter, status int, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(data)
|
|
}
|
|
|
|
func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
|
|
return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
|
|
ctx := context.Background()
|
|
|
|
var network *Network
|
|
if id := params.ByName("netid"); id != "" {
|
|
s.mtx.Lock()
|
|
var ok bool
|
|
network, ok = s.networks[id]
|
|
s.mtx.Unlock()
|
|
if !ok {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
ctx = context.WithValue(ctx, "network", network)
|
|
}
|
|
|
|
if id := params.ByName("nodeid"); id != "" {
|
|
if network == nil {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
node := network.GetNode(adapters.NewNodeIdFromHex(id))
|
|
if node == nil {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
ctx = context.WithValue(ctx, "node", node)
|
|
}
|
|
|
|
if id := params.ByName("peerid"); id != "" {
|
|
if network == nil {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
peer := network.GetNode(adapters.NewNodeIdFromHex(id))
|
|
if peer == nil {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
ctx = context.WithValue(ctx, "peer", peer)
|
|
}
|
|
|
|
handler(w, req.WithContext(ctx))
|
|
}
|
|
}
|