Skip to content

Commit

Permalink
done
Browse files Browse the repository at this point in the history
  • Loading branch information
likesToEatFish committed Sep 29, 2024
1 parent df91385 commit 48d336d
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 1 deletion.
161 changes: 161 additions & 0 deletions ignite/cmd/model/testnet_multi_node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package cmdmodel

import (
"context"
"fmt"
"os/exec"
"path/filepath"
"strconv"
"syscall"

tea "github.com/charmbracelet/bubbletea"
"github.com/ignite/cli/v29/ignite/services/chain"
)

type NodeStatus int

const (
Stopped NodeStatus = iota
Running
)

type Model struct {
appd string
args chain.MultiNodeArgs
ctx context.Context

nodeStatuses []NodeStatus
pids []int // Store the PIDs of the running processes
numNodes int // Number of nodes
}

type ToggleNodeMsg struct {
nodeIdx int
}

type UpdateStatusMsg struct {
nodeIdx int
status NodeStatus
}

// Initialize the model
func NewModel(chainname string, ctx context.Context, args chain.MultiNodeArgs) Model {
numNodes, err := strconv.Atoi(args.NumValidator)
if err != nil {
panic(err)
}
return Model{
appd: chainname + "d",
args: args,
ctx: ctx,
nodeStatuses: make([]NodeStatus, numNodes), // initial states of nodes
pids: make([]int, numNodes),
numNodes: numNodes,
}
}

// Implement the Update function
func (m Model) Init() tea.Cmd {
return nil
}

// ToggleNode toggles the state of a node
func ToggleNode(nodeIdx int) tea.Cmd {
return func() tea.Msg {
return ToggleNodeMsg{nodeIdx: nodeIdx}
}
}

// Run or stop the node based on its status
func RunNode(nodeIdx int, start bool, pid *int, args chain.MultiNodeArgs, appd string) tea.Cmd {
return func() tea.Msg {
if start {
nodeHome := filepath.Join(args.OutputDir, args.NodeDirPrefix+strconv.Itoa(nodeIdx))
// Create the command to run in background as a daemon
cmd := exec.Command(appd, "start", "--home", nodeHome)

// Start the process as a daemon
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true, // Ensure it runs in a new process group
}

err := cmd.Start() // Start the node in the background
if err != nil {
fmt.Printf("Failed to start node %d: %v\n", nodeIdx+1, err)
return UpdateStatusMsg{nodeIdx: nodeIdx, status: Stopped}
}

*pid = cmd.Process.Pid // Store the PID
go cmd.Wait() // Let the process run asynchronously
return UpdateStatusMsg{nodeIdx: nodeIdx, status: Running}
} else {
// Use kill to stop the node process by PID
if *pid != 0 {
err := syscall.Kill(-*pid, syscall.SIGTERM) // Stop the daemon process
if err != nil {
fmt.Printf("Failed to stop node %d: %v\n", nodeIdx+1, err)
} else {
*pid = 0 // Reset PID after stopping
}
}
return UpdateStatusMsg{nodeIdx: nodeIdx, status: Stopped}
}
}
}

// Stop all nodes
func (m *Model) StopAllNodes() {
for i := 0; i < m.numNodes; i++ {
if m.nodeStatuses[i] == Running {
RunNode(i, false, &m.pids[i], m.args, m.appd)() // Stop node
}
}
}

// Update handles messages and updates the model
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q":
m.StopAllNodes() // Stop all nodes before quitting
return m, tea.Quit
default:
// Check for numbers from 1 to numNodes
for i := 0; i < m.numNodes; i++ {
if msg.String() == fmt.Sprintf("%d", i+1) {
return m, ToggleNode(i)
}
}
}

case ToggleNodeMsg:
if m.nodeStatuses[msg.nodeIdx] == Running {
return m, RunNode(msg.nodeIdx, false, &m.pids[msg.nodeIdx], m.args, m.appd) // Stop node
}
return m, RunNode(msg.nodeIdx, true, &m.pids[msg.nodeIdx], m.args, m.appd) // Start node

case UpdateStatusMsg:
m.nodeStatuses[msg.nodeIdx] = msg.status
return m, nil
}

return m, nil
}

// View renders the interface
func (m Model) View() string {
statusText := func(status NodeStatus) string {
if status == Running {
return "[Running]"
}
return "[Stopped]"
}

output := "Node Control:\n"
for i := 0; i < m.numNodes; i++ {
output += fmt.Sprintf("%d. Node %d %s\n", i+1, i+1, statusText(m.nodeStatuses[i]))
}
output += "Press q to quit.\n"
return output
}
18 changes: 17 additions & 1 deletion ignite/cmd/testnet_multi_node.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package ignitecmd

import (
// "fmt"
"math/rand"
"strconv"
"time"

"cosmossdk.io/math"
"github.com/spf13/cobra"

sdk "github.com/cosmos/cosmos-sdk/types"

tea "github.com/charmbracelet/bubbletea"
cmdmodel "github.com/ignite/cli/v29/ignite/cmd/model"
"github.com/ignite/cli/v29/ignite/config/chain/base"
"github.com/ignite/cli/v29/ignite/pkg/cliui"
"github.com/ignite/cli/v29/ignite/services/chain"
Expand Down Expand Up @@ -98,9 +102,21 @@ func testnetMultiNode(cmd *cobra.Command, session *cliui.Session) error {
ValidatorsStakeAmount: amountDetails,
OutputDir: cfg.MultiNode.OutputDir,
NumValidator: strconv.Itoa(numVal),
NodeDirPrefix: "validator", //node
}

return c.TestnetMultiNode(cmd.Context(), args)
//initialized 3 node directories
err = c.TestnetMultiNode(cmd.Context(), args)
if err != nil {
return err
}

time.Sleep(7 * time.Second)

// c.TestnetStartMultiNode(cmd.Context(), args)
m := cmdmodel.NewModel(c.Name(), cmd.Context(), args)
_, err = tea.NewProgram(m).Run()
return err
}

// getValidatorAmountStake returns the number of validators and the amountStakes arg from config.MultiNode
Expand Down
6 changes: 6 additions & 0 deletions ignite/pkg/chaincmd/chaincmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ func (c ChainCmd) StartCommand(options ...string) step.Option {
command := append([]string{
commandStart,
}, options...)
fmt.Println(command)
return c.daemonCommand(command)
}

Expand Down Expand Up @@ -643,6 +644,11 @@ func (c ChainCmd) daemonCommand(command []string) step.Option {
return step.Exec(c.appCmd, c.attachHome(command)...)
}

// daemonCommand returns the daemon command from the given command (including the home flag).
func (c ChainCmd) daemonCommandIncludedHomeFlag(command []string) step.Option {
return step.Exec(c.appCmd, command...)
}

// cliCommand returns the cli command from the provided command.
func (c ChainCmd) cliCommand(command []string) step.Option {
return step.Exec(c.appCmd, c.attachHome(command)...)
Expand Down
23 changes: 23 additions & 0 deletions ignite/pkg/chaincmd/in-place-testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ func MultiNodeWithValidatorsStakeAmount(satkeAmounts string) MultiNodeOption {
}
}

func MultiNodeWithHome(home string) MultiNodeOption {
return func(s []string) []string {
if len(home) > 0 {
return append(s, optionHome, home)
}
return s
}
}

// TestnetMultiNodeCommand return command to start testnet multinode.
func (c ChainCmd) TestnetMultiNodeCommand(options ...MultiNodeOption) step.Option {
command := []string{
Expand All @@ -99,3 +108,17 @@ func (c ChainCmd) TestnetMultiNodeCommand(options ...MultiNodeOption) step.Optio

return c.daemonCommand(command)
}

// TestnetMultiNodeCommand return command to start testnet multinode.
func (c ChainCmd) TestnetStartMultiNodeCommand(options ...MultiNodeOption) step.Option {
command := []string{
commandStart,
}

// Apply the options provided by the user
for _, apply := range options {
command = apply(command)
}

return c.daemonCommandIncludedHomeFlag(command)
}
13 changes: 13 additions & 0 deletions ignite/pkg/chaincmd/runner/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ func (r Runner) MultiNode(ctx context.Context, options ...chaincmd.MultiNodeOpti
)
}

func (r Runner) StartMultiNode(ctx context.Context, options ...chaincmd.MultiNodeOption) error {
runOptions := runOptions{
wrappedStdErrMaxLen: 50000,
stdout: os.Stdout,
stderr: os.Stderr,
}
return r.run(
ctx,
runOptions,
r.chainCmd.TestnetStartMultiNodeCommand(options...),
)
}

// Gentx generates a genesis tx carrying a self delegation.
func (r Runner) Gentx(
ctx context.Context,
Expand Down
56 changes: 56 additions & 0 deletions ignite/services/chain/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"os"
"path/filepath"
"strconv"
"sync"
// "time"

"github.com/nqd/flat"
"github.com/pelletier/go-toml"
Expand Down Expand Up @@ -58,6 +61,59 @@ func (c Chain) MultiNode(ctx context.Context, runner chaincmdrunner.Runner, args
return err
}

func (c Chain) StartMultiNode(ctx context.Context, runner chaincmdrunner.Runner, args MultiNodeArgs) error {
numVal, err := strconv.Atoi(args.NumValidator)
if err != nil {
return err
}

// Kênh để nhận lỗi từ các goroutines
errCh := make(chan error, numVal)
var wg sync.WaitGroup

// Tạo context có thể hủy bỏ
ctx, cancel := context.WithCancel(ctx)
defer cancel()

// Tạo các goroutines để chạy các node
for i := 0; i < numVal; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
home := filepath.Join(args.OutputDir, args.NodeDirPrefix+strconv.Itoa(i))

// Kiểm tra nếu context bị hủy
select {
case <-ctx.Done():
return // Kết thúc goroutine nếu context bị hủy
default:
// Chạy node
errRunNode := runner.StartMultiNode(ctx, chaincmd.MultiNodeWithHome(home))

// Nếu có lỗi, gửi lỗi vào kênh và hủy toàn bộ goroutines
if errRunNode != nil {
errCh <- errRunNode
cancel() // Hủy tất cả goroutines khác
}
}
}(i)
}

// Chờ tất cả các goroutines hoàn thành
wg.Wait()

// Đóng kênh sau khi tất cả các goroutine hoàn tất
close(errCh)

// Kiểm tra lỗi từ các goroutines
if len(errCh) > 0 {
// Lấy lỗi đầu tiên nếu có lỗi
return <-errCh
}

return nil
}

// Start wraps the "appd start" command to begin running a chain from the daemon.
func (c Chain) Start(ctx context.Context, runner chaincmdrunner.Runner, cfg *chainconfig.Config) error {
validator, err := chainconfig.FirstValidator(cfg)
Expand Down
23 changes: 23 additions & 0 deletions ignite/services/chain/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type MultiNodeArgs struct {
OutputDir string
NumValidator string
ValidatorsStakeAmount string
NodeDirPrefix string
}

func (c Chain) TestnetMultiNode(ctx context.Context, args MultiNodeArgs) error {
Expand All @@ -64,3 +65,25 @@ func (c Chain) TestnetMultiNode(ctx context.Context, args MultiNodeArgs) error {
}
return nil
}

func (c Chain) TestnetStartMultiNode(ctx context.Context, args MultiNodeArgs) error {
commands, err := c.Commands(ctx)
if err != nil {
return err
}

// make sure that config.yml exists
if c.options.ConfigFile != "" {
if _, err := os.Stat(c.options.ConfigFile); err != nil {
return err
}
} else if _, err := chainconfig.LocateDefault(c.app.Path); err != nil {
return err
}

err = c.StartMultiNode(ctx, commands, args)
if err != nil {
return err
}
return nil
}

0 comments on commit 48d336d

Please sign in to comment.