Skip to content

Commit

Permalink
refactor: status, participation and state interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
PhearZero committed Dec 17, 2024
1 parent 91e23f8 commit 911ad15
Show file tree
Hide file tree
Showing 21 changed files with 393 additions and 321 deletions.
69 changes: 24 additions & 45 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import (
"github.com/algorandfoundation/algorun-tui/cmd/configure"
"github.com/algorandfoundation/algorun-tui/cmd/node"
"github.com/algorandfoundation/algorun-tui/cmd/utils"
"github.com/algorandfoundation/algorun-tui/cmd/utils/explanations"
"github.com/algorandfoundation/algorun-tui/internal"
"github.com/algorandfoundation/algorun-tui/internal/algod"
"github.com/algorandfoundation/algorun-tui/ui"
"github.com/algorandfoundation/algorun-tui/ui/explanations"
"github.com/algorandfoundation/algorun-tui/ui/style"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/log"
Expand Down Expand Up @@ -38,66 +38,49 @@ var (
return err
}

if viper.GetString("algod-endpoint") == "" {
endpoint := viper.GetString("algod-endpoint")
token := viper.GetString("algod-token")

if endpoint == "" {
return fmt.Errorf(style.Red.Render("algod-endpoint is required") + explanations.NodeNotFound)
}

if viper.GetString("algod-token") == "" {
if token == "" {
return fmt.Errorf(style.Red.Render("algod-token is required"))
}

// Create the dependencies
ctx := context.Background()
client, err := algod.GetClient(viper.GetString("algod-endpoint"), viper.GetString("algod-token"))
client, err := algod.GetClient(endpoint, token)
cobra.CheckErr(err)

httpPkg := new(api.HttpPkg)
algodStatus, v, err := algod.NewStatus(ctx, client, httpPkg)
if err != nil {
return fmt.Errorf(
style.Red.Render("failed to get status: %s")+explanations.Unreachable,
err)
} else if v.StatusCode() == 401 {

// Fetch the state and handle any creation errors
state, stateResponse, err := internal.NewStateModel(ctx, client, httpPkg)
if stateResponse.StatusCode() == 401 {
return fmt.Errorf(
style.Red.Render("failed to get status: Unauthorized") + explanations.TokenInvalid)
} else if v.StatusCode() != 200 {
return fmt.Errorf(
style.Red.Render("failed to get status: error code %d")+explanations.TokenNotAdmin,
v.StatusCode())
}

partkeys, err := internal.GetPartKeys(ctx, client)
if err != nil {
if stateResponse.StatusCode() > 300 {
return fmt.Errorf(
style.Red.Render("failed to get participation keys: %s")+
explanations.TokenNotAdmin,
err)
}
state := internal.StateModel{
Status: algodStatus,
Metrics: internal.MetricsModel{
RoundTime: 0,
TPS: 0,
RX: 0,
TX: 0,
},
ParticipationKeys: partkeys,

Client: client,
Context: ctx,
style.Red.Render("failed to get status: error code %d")+explanations.TokenNotAdmin,
stateResponse.StatusCode())
}
state.Accounts, err = internal.AccountsFromState(&state, new(internal.Clock), client)
cobra.CheckErr(err)
// Fetch current state
//_, err = state.Status.Fetch(ctx, client, new(internal.HttpPkg))
//cobra.CheckErr(err)

m, err := ui.NewViewportViewModel(&state, client)
// Construct the TUI Model from the State
m, err := ui.NewViewportViewModel(state, client)
cobra.CheckErr(err)

// Construct the TUI Application
p := tea.NewProgram(
m,
tea.WithAltScreen(),
tea.WithFPS(120),
)

// Watch for State Updates on a separate thread
// TODO: refactor into context aware watcher without callbacks
go func() {
state.Watch(func(status *internal.StateModel, err error) {
if err == nil {
Expand All @@ -109,18 +92,14 @@ var (
}
}, ctx, client)
}()

// Execute the TUI Application
_, err = p.Run()
return err
},
}, &algodEndpoint, &algodToken)
)

func check(err interface{}) {
if err != nil {
panic(err)
}
}

// Handle global flags and set usage templates
func init() {
log.SetReportTimestamp(false)
Expand Down
10 changes: 0 additions & 10 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmd

import (
"errors"
"github.com/algorandfoundation/algorun-tui/cmd/utils"
"github.com/spf13/viper"
"os"
Expand Down Expand Up @@ -108,13 +107,4 @@ func Test_ExecuteRootCommand(t *testing.T) {
}
clearViper()
})

t.Run("check error", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
}
}()
check(errors.New("test"))
})
}
43 changes: 23 additions & 20 deletions cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"errors"
"fmt"
"github.com/algorandfoundation/algorun-tui/api"
"github.com/algorandfoundation/algorun-tui/cmd/utils"
"github.com/algorandfoundation/algorun-tui/cmd/utils/explanations"
"github.com/algorandfoundation/algorun-tui/internal"
"github.com/algorandfoundation/algorun-tui/internal/algod"
"github.com/algorandfoundation/algorun-tui/ui"
Expand All @@ -28,36 +30,37 @@ var statusCmd = utils.WithAlgodFlags(&cobra.Command{
if viper.GetString("algod-endpoint") == "" {
return errors.New(style.Magenta("algod-endpoint is required"))
}

ctx := context.Background()
httpPkg := new(api.HttpPkg)
// Get Algod from configuration
client, err := algod.GetClient(viper.GetString("algod-endpoint"), viper.GetString("algod-token"))
cobra.CheckErr(err)
state := internal.StateModel{
Status: algod.Status{
State: "SYNCING",
Version: "N/A",
Network: "N/A",
Voting: false,
NeedsUpdate: true,
LastRound: 0,
},
Metrics: internal.MetricsModel{
RoundTime: 0,
TPS: 0,
RX: 0,
TX: 0,
},
ParticipationKeys: nil,

// Fetch the state and handle any creation errors
state, stateResponse, err := internal.NewStateModel(ctx, client, httpPkg)
if stateResponse.StatusCode() == 401 {
return fmt.Errorf(
style.Red.Render("failed to get status: Unauthorized") + explanations.TokenInvalid)
}
if stateResponse.StatusCode() > 300 {
return fmt.Errorf(
style.Red.Render("failed to get status: error code %d")+explanations.TokenNotAdmin,
stateResponse.StatusCode())
}
//_, err = state.Status.Fetch(context.Background(), client, new(internal.HttpPkg))
//cobra.CheckErr(err)
cobra.CheckErr(err)

// Create the TUI
view := ui.MakeStatusViewModel(&state)
view := ui.MakeStatusViewModel(state)

p := tea.NewProgram(view, tea.WithAltScreen())
go func() {
// Watch for State Changes
state.Watch(func(status *internal.StateModel, err error) {
if err != nil {
state.Stop()
}
cobra.CheckErr(err)
// Send the state to the TUI
p.Send(state)
}, context.Background(), client)
}()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@ package explanations

import (
"github.com/algorandfoundation/algorun-tui/ui/style"
"github.com/charmbracelet/lipgloss"
)

var NodeNotFound = "\n\nExplanation: algorun could not find your node automatically. Provide --algod-endpoint and --algod-token, or set the goal-compatible ALGORAND_DATA environment variable to the algod data directory, e.g. /var/lib/algorand\n"

var NodeNotFound = lipgloss.NewStyle().
PaddingTop(1).
PaddingBottom(1).
Render(lipgloss.JoinHorizontal(lipgloss.Left,
style.Cyan.Render("Explanation"),
": ",
"algorun could not find your node automatically.",
"Provide ",
style.Bold("--algod-endpoint"),
" and ",
style.Bold("--algod-token"),
"or set the goal-compatible ",
style.Bold("ALGORAND_DATA"),
" environment variable to the algod data directory, ",
style.Bold("e.g. /var/lib/algorand"),
))
var Unreachable = "\n\nExplanation: Could not reach algod. Check that algod is running and the provided connection arguments.\n"

var TokenInvalid = "\n\nExplanation: algod token is invalid. Algorun requires the " + style.BoldUnderline("admin token") + " for algod. You can find this in the algod.admin.token file in the algod data directory.\n"
Expand Down
63 changes: 63 additions & 0 deletions internal/algod/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package algod

import (
"context"
"errors"
"github.com/algorandfoundation/algorun-tui/api"
"time"
)

type BlockMetrics struct {
AvgTime time.Duration
TPS float64
}

func GetBlockMetrics(ctx context.Context, client api.ClientWithResponsesInterface, round uint64, window int) (BlockMetrics, api.ResponseInterface, error) {
var avgs = BlockMetrics{
AvgTime: 0,
TPS: 0,
}
var format api.GetBlockParamsFormat = "json"

// Current Block
currentBlockResponse, err := client.GetBlockWithResponse(ctx, int(round), &api.GetBlockParams{
Format: &format,
})
if err != nil {
return avgs, currentBlockResponse, err
}
if currentBlockResponse.StatusCode() != 200 {
return avgs, currentBlockResponse, errors.New(currentBlockResponse.Status())
}

// Previous Block Response
previousBlockResponse, err := client.GetBlockWithResponse(ctx, int(round)-window, &api.GetBlockParams{
Format: &format,
})
if err != nil {
return avgs, previousBlockResponse, err
}
if previousBlockResponse.StatusCode() != 200 {
return avgs, previousBlockResponse, errors.New(previousBlockResponse.Status())
}

// Push to the transactions count list
aTimestampRes := currentBlockResponse.JSON200.Block["ts"]
bTimestampRes := previousBlockResponse.JSON200.Block["ts"]
if aTimestampRes == nil || bTimestampRes == nil {
return avgs, previousBlockResponse, nil
}
aTimestamp := time.Duration(aTimestampRes.(float64)) * time.Second
bTimestamp := time.Duration(bTimestampRes.(float64)) * time.Second

// Transaction Counter
aTransactions := currentBlockResponse.JSON200.Block["tc"]
bTransactions := previousBlockResponse.JSON200.Block["tc"]

avgs.AvgTime = time.Duration((int(aTimestamp - bTimestamp)) / window)
if aTransactions != nil && bTransactions != nil {
avgs.TPS = (aTransactions.(float64) - bTransactions.(float64)) / (float64(window) * avgs.AvgTime.Seconds())
}

return avgs, currentBlockResponse, nil
}
2 changes: 1 addition & 1 deletion internal/block_test.go → internal/algod/block_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package internal
package algod

import (
"context"
Expand Down
Loading

0 comments on commit 911ad15

Please sign in to comment.