diff --git a/.github/README.md b/.github/README.md index 11c8e05..a59a2e6 100644 --- a/.github/README.md +++ b/.github/README.md @@ -7,7 +7,23 @@ networkHub is a versatile framework designed to streamline the process of launch networkHub enables teams to quickly deploy custom networks, facilitating development and testing in a controlled environment. This framework is especially beneficial for protocol and dapp teams looking to experiment with network configurations and behaviors without the overhead of setting up infrastructure from scratch. ## Quick start -- **Launch Pre-configured Network (ThreeMasterNodeNetwork)**: +- **Launch Pre-configured Network via command line**: + ```bash + # Setup the preset network + > go run cmd/main.go cmd preset threeMasterNodesNetwork /Users/pedro/go/src/github.com/vechain/thor/bin/thor + ... + 2024/06/07 17:31:43 INFO preset network config was successful... networkId=localthreeMaster + + # Start preset network + > go run cmd/main.go cmd start localthreeMaster + 2024/06/07 17:31:57 INFO Registered preset network networkId=threeMasterNodesNetwork + 2024/06/07 17:31:57 INFO Registered preset network networkId=sixNodesNetwork + 2024/06/07 17:31:57 INFO Starting network... ID=localthreeMaster + ... + ``` + + +- **Launch Pre-configured Network via API server**: ```bash # Start the networkhub api go run ./cmd/main.go api diff --git a/.gitignore b/.gitignore index d28e438..674d014 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ package.json coverage.out .idea +network.json +networks_db.json diff --git a/cmd/cmd/cmd.go b/cmd/cmd/cmd.go new file mode 100644 index 0000000..080bcfb --- /dev/null +++ b/cmd/cmd/cmd.go @@ -0,0 +1,132 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "log/slog" + "os" + "os/signal" + "path/filepath" + "syscall" + + "github.com/vechain/networkhub/environments/local" + "github.com/vechain/networkhub/hub" + "github.com/vechain/networkhub/preset" + + "github.com/spf13/cobra" + + cmdentrypoint "github.com/vechain/networkhub/entrypoint/cmd" +) + +func setup() *cmdentrypoint.Cmd { + envManager := hub.NewNetworkHub() + envManager.RegisterEnvironment("local", local.NewLocalEnv) + + presets := preset.NewPresetNetworks() + presets.Register("threeMasterNodesNetwork", preset.LocalThreeMasterNodesNetwork) + presets.Register("sixNodesNetwork", preset.LocalSixNodesNetwork) + + execDir, err := os.Getwd() // TODO might want to make this configurable in the future ? + if err != nil { + panic(fmt.Errorf("unable to use current directory: %w", err)) + } + + cmdEntrypoint := cmdentrypoint.New(envManager, presets, filepath.Join(execDir, "networks_db.json")) + if err = cmdEntrypoint.LoadExistingNetworks(); err != nil { + panic(fmt.Errorf("unable to load existing networks: %w", err)) + } + + return cmdEntrypoint +} + +var cmdCmd = &cobra.Command{ + Use: "cmd", + Short: "Directly uses NetworkHub", +} + +var startCmd = &cobra.Command{ + Use: "start [network-id]", + Short: "Start a specific network", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cmdManager := setup() + networkID := args[0] + slog.Info("Starting network...", "ID", networkID) + + // Channel to listen for interrupt signals + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + + go func() { + err := cmdManager.Start(networkID) + if err != nil { + slog.Error("unable to start network", "err", err) + return + } + slog.Info("network started successfully...") + }() + + // Wait for interrupt signal + <-sigChan + slog.Info("Interrupt signal received. Stopping the network...") + + err := cmdManager.Stop(networkID) + if err != nil { + slog.Error("unable to stop network", "err", err) + } else { + slog.Info("network stopped successfully.") + } + }, +} + +var configureCmd = &cobra.Command{ + Use: "config [network-json-config]", + Short: "Configures a specific network", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cmdManager := setup() + + // Read from the specified file + data, err := ioutil.ReadFile(args[0]) + if err != nil { + fmt.Printf("Error reading config file: %v\n", err) + os.Exit(1) + } + + slog.Info("Configuring network...") + + networkID, err := cmdManager.Config(string(data)) + if err != nil { + slog.Error("unable to config network", "err", err) + return + } + slog.Info("network config was successful...", "networkId", networkID) + }, +} + +var presetCmd = &cobra.Command{ + Use: "preset [preset-name] [preset-thor-path]", + Short: "Configures a preset network", + Args: cobra.MinimumNArgs(2), + Run: func(cmd *cobra.Command, args []string) { + cmdManager := setup() + + presetNetwork := args[0] + presetArtifactPath := args[1] + + slog.Info("Configuring network...") + + networkID, err := cmdManager.Preset(presetNetwork, presetArtifactPath) + if err != nil { + slog.Error("unable to config preset network", "err", err) + return + } + slog.Info("preset network config was successful...", "networkId", networkID) + }, +} + +func init() { + + cmdCmd.AddCommand(startCmd, configureCmd, presetCmd) + rootCmd.AddCommand(cmdCmd) +} diff --git a/entrypoint/api/http_api.go b/entrypoint/api/http_api.go index 498ad71..d0c1063 100644 --- a/entrypoint/api/http_api.go +++ b/entrypoint/api/http_api.go @@ -12,13 +12,13 @@ import ( ) type Server struct { - entrypoint *hub.NetworkHub + networkHub *hub.NetworkHub presets *preset.Networks } func New(envMgr *hub.NetworkHub, presets *preset.Networks) *Server { return &Server{ - entrypoint: envMgr, + networkHub: envMgr, presets: presets, } } @@ -63,7 +63,7 @@ func (s *Server) presetHandler(w http.ResponseWriter, r *http.Request) { return } - networkID, err := s.entrypoint.LoadNetworkConfig(networkCfg) + networkID, err := s.networkHub.LoadNetworkConfig(networkCfg) if err != nil { http.Error(w, fmt.Sprintf("Unable to load network config - %s", err.Error()), http.StatusInternalServerError) return @@ -85,7 +85,7 @@ func (s *Server) configHandler(w http.ResponseWriter, r *http.Request) { return } - networkID, err := s.entrypoint.LoadNetworkConfig(&networkCfg) + networkID, err := s.networkHub.LoadNetworkConfig(&networkCfg) if err != nil { http.Error(w, fmt.Sprintf("Unable to load network config - %s", err.Error()), http.StatusInternalServerError) return @@ -109,7 +109,7 @@ func (s *Server) startHandler(w http.ResponseWriter, r *http.Request) { return } - err := s.entrypoint.StartNetwork(networkID) + err := s.networkHub.StartNetwork(networkID) if err != nil { http.Error(w, fmt.Sprintf("Unable to start network - %s", err.Error()), http.StatusInternalServerError) return @@ -133,7 +133,7 @@ func (s *Server) stopHandler(w http.ResponseWriter, r *http.Request) { return } - err := s.entrypoint.StopNetwork(networkID) + err := s.networkHub.StopNetwork(networkID) if err != nil { http.Error(w, fmt.Sprintf("Unable to stop network - %s", err.Error()), http.StatusInternalServerError) return diff --git a/entrypoint/cmd/cmd.go b/entrypoint/cmd/cmd.go new file mode 100644 index 0000000..740a75c --- /dev/null +++ b/entrypoint/cmd/cmd.go @@ -0,0 +1,87 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "github.com/vechain/networkhub/hub" + "github.com/vechain/networkhub/network" + "github.com/vechain/networkhub/preset" +) + +type Cmd struct { + networkHub *hub.NetworkHub + presets *preset.Networks + storage *Storage +} + +func (c *Cmd) Stop(id string) error { + return c.networkHub.StopNetwork(id) +} + +func (c *Cmd) Start(id string) error { + return c.networkHub.StartNetwork(id) +} + +func (c *Cmd) Config(config string) (string, error) { + var netCfg network.Network + + if err := json.Unmarshal([]byte(config), &netCfg); err != nil { + return "", err + } + return c.config(&netCfg) +} + +func (c *Cmd) LoadExistingNetworks() error { + nets, err := c.storage.LoadExistingNetworks() + if err != nil { + return fmt.Errorf("unable to load existing networks: %w", err) + } + + for networkID, net := range nets { + loadedID, err := c.networkHub.LoadNetworkConfig(net) + if err != nil { + return err + } + + if networkID != loadedID { + return fmt.Errorf("unexpected networkID loaded: storedID:%s configuredID:%s", networkID, loadedID) + } + } + + return nil +} + +func (c *Cmd) Preset(presetNetwork string, presetConfig string) (string, error) { + netCfg, err := c.presets.Load(presetNetwork, &preset.APIConfigPayload{ArtifactPath: presetConfig}) + if err != nil { + return "", fmt.Errorf("unable to load network preset: %w", err) + } + return c.config(netCfg) +} + +func New(networkHub *hub.NetworkHub, presets *preset.Networks, storagePath string) *Cmd { + return &Cmd{ + networkHub: networkHub, + presets: presets, + storage: NewStorage(storagePath), + } +} + +func (c *Cmd) config(netCfg *network.Network) (string, error) { + networkID, err := c.networkHub.LoadNetworkConfig(netCfg) + if err != nil { + return "", fmt.Errorf("unable to load config: %w", err) + } + + networkInst, err := c.networkHub.GetNetwork(networkID) + if err != nil { + return "", fmt.Errorf("unable to retrieve network: %w", err) + } + + err = c.storage.Store(networkID, networkInst) + if err != nil { + return "", fmt.Errorf("unable to store network: %w", err) + } + + return networkID, nil +} diff --git a/entrypoint/cmd/storage.go b/entrypoint/cmd/storage.go new file mode 100644 index 0000000..7503d09 --- /dev/null +++ b/entrypoint/cmd/storage.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "github.com/vechain/networkhub/network" + "io/ioutil" + "log/slog" + "os" +) + +type StorageJson struct { + Network map[string]*network.Network `json:"network"` +} + +type Storage struct { + path string +} + +func (s *Storage) Store(networkID string, net *network.Network) error { + storageJson, err := s.LoadExistingNetworks() + if err != nil { + return fmt.Errorf("unable to load existing networks: %w", err) + } + + // Add/Update the network entry + storageJson[networkID] = net + + // Marshal the updated data + data, err := json.MarshalIndent(storageJson, "", " ") + if err != nil { + return err + } + + // Write the updated data back to file + err = ioutil.WriteFile(s.path, data, 0644) + if err != nil { + return err + } + + slog.Info("Network saved to file", "filepath", s.path) + return nil +} + +func (s *Storage) LoadExistingNetworks() (map[string]*network.Network, error) { + // Initialize an empty StorageJson + storageJson := make(map[string]*network.Network) + + // Check if file exists + if _, err := os.Stat(s.path); err == nil { + // File exists, load the current data + fileData, err := ioutil.ReadFile(s.path) + if err != nil { + return nil, err + } + + err = json.Unmarshal(fileData, &storageJson) + if err != nil { + return nil, err + } + } + return storageJson, nil +} + +func NewStorage(path string) *Storage { + return &Storage{ + path: path, + } +} diff --git a/environments/local/local_test.go b/environments/local/local_test.go index cc8b4bd..68d0d8a 100644 --- a/environments/local/local_test.go +++ b/environments/local/local_test.go @@ -122,12 +122,13 @@ var networkJSON = fmt.Sprintf(`{ }`, genesis, genesis, genesis) func TestLocal(t *testing.T) { - t.Skip() + //t.Skip() networkCfg, err := network.NewNetwork( network.WithJSON(networkJSON), ) require.NoError(t, err) + fmt.Println(networkJSON) localEnv := NewLocalEnv() _, err = localEnv.LoadConfig(networkCfg) require.NoError(t, err) diff --git a/hub/hub.go b/hub/hub.go index bb44702..d9cec42 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -8,14 +8,16 @@ import ( ) type NetworkHub struct { - envFuncs map[string]func() environments.Actions - networks map[string]environments.Actions + envFuncs map[string]func() environments.Actions + configuredNetworks map[string]environments.Actions + networks map[string]*network.Network } func NewNetworkHub() *NetworkHub { return &NetworkHub{ - envFuncs: map[string]func() environments.Actions{}, - networks: map[string]environments.Actions{}, + envFuncs: map[string]func() environments.Actions{}, + configuredNetworks: map[string]environments.Actions{}, + networks: map[string]*network.Network{}, } } @@ -30,13 +32,14 @@ func (e *NetworkHub) LoadNetworkConfig(cfg *network.Network) (string, error) { if err != nil { return "", fmt.Errorf("unable to load config - %w", err) } - e.networks[networkID] = env + e.configuredNetworks[networkID] = env + e.networks[networkID] = cfg return networkID, nil } func (e *NetworkHub) StartNetwork(networkID string) error { - netwk, ok := e.networks[networkID] + netwk, ok := e.configuredNetworks[networkID] if !ok { return fmt.Errorf("network %s is not configured", networkID) } @@ -44,7 +47,7 @@ func (e *NetworkHub) StartNetwork(networkID string) error { } func (e *NetworkHub) StopNetwork(networkID string) error { - netwk, ok := e.networks[networkID] + netwk, ok := e.configuredNetworks[networkID] if !ok { return fmt.Errorf("network %s is not configured", networkID) } @@ -52,7 +55,7 @@ func (e *NetworkHub) StopNetwork(networkID string) error { } func (e *NetworkHub) InfoNetwork(networkID string) error { - netwk, ok := e.networks[networkID] + netwk, ok := e.configuredNetworks[networkID] if !ok { return fmt.Errorf("network %s is not configured", networkID) } @@ -62,3 +65,11 @@ func (e *NetworkHub) InfoNetwork(networkID string) error { func (e *NetworkHub) RegisterEnvironment(id string, env func() environments.Actions) { e.envFuncs[id] = env } + +func (e *NetworkHub) GetNetwork(id string) (*network.Network, error) { + loadedNetwork, ok := e.networks[id] + if !ok { + return nil, fmt.Errorf("network not found") + } + return loadedNetwork, nil +} diff --git a/preset/preset.go b/preset/preset.go index 6b655ad..b0d14cf 100644 --- a/preset/preset.go +++ b/preset/preset.go @@ -2,6 +2,7 @@ package preset import ( "fmt" + "log/slog" "math/big" "github.com/vechain/networkhub/network" @@ -24,6 +25,7 @@ func NewPresetNetworks() *Networks { func (p *Networks) Register(id string, preset *network.Network) { p.presets[id] = preset + slog.Info("Registered preset network", "networkId", id) } func (p *Networks) Load(id string, configPayload *APIConfigPayload) (*network.Network, error) {