Skip to content

Commit

Permalink
feat(ui): show upgrade voting status (progress %, yes%) and scheduled…
Browse files Browse the repository at this point in the history
… vote with ETA

Co-authored-by: Tasos Bitsios <[email protected]>
  • Loading branch information
2 people authored and PhearZero committed Jan 14, 2025
1 parent dc18ae4 commit 3b67f7d
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 82 deletions.
3 changes: 2 additions & 1 deletion internal/algod/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"context"
"errors"
"fmt"
"time"

"github.com/algorandfoundation/nodekit/internal/algod/participation"
"github.com/algorandfoundation/nodekit/internal/algod/utils"
"github.com/algorandfoundation/nodekit/internal/system"
"time"

"github.com/algorand/go-algorand-sdk/v2/types"
"github.com/algorandfoundation/nodekit/api"
Expand Down
19 changes: 13 additions & 6 deletions internal/algod/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package algod

import (
"context"
"testing"
"time"

"github.com/algorandfoundation/nodekit/api"
"github.com/algorandfoundation/nodekit/internal/test"
"github.com/algorandfoundation/nodekit/internal/test/mock"
"github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider"
"github.com/stretchr/testify/assert"
"testing"
"time"
)

func Test_AccountsFromState(t *testing.T) {
Expand Down Expand Up @@ -73,10 +74,16 @@ func Test_AccountsFromState(t *testing.T) {
TX: 2048,
},
Status: Status{
State: "WATCHING",
Version: "v0.0.0-test",
Network: "tuinet",
Voting: false,
State: "WATCHING",
Version: "v0.0.0-test",
Network: "tuinet",

UpgradeVoteRounds: 0,
UpgradeYesVotes: 0,
UpgradeNoVotes: 0,
UpgradeVotes: 0,
UpgradeVotesRequired: 0,

NeedsUpdate: false,
LastRound: 1337,
Client: client,
Expand Down
3 changes: 2 additions & 1 deletion internal/algod/algod.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package algod

import (
"fmt"
"runtime"

"github.com/algorandfoundation/nodekit/internal/algod/linux"
"github.com/algorandfoundation/nodekit/internal/algod/mac"
"github.com/algorandfoundation/nodekit/internal/system"
"runtime"
)

// UnsupportedOSError indicates that the current operating system is not supported for the requested operation.
Expand Down
45 changes: 39 additions & 6 deletions internal/algod/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ type Status struct {
// Network represents the name of the network the status is associated with.
Network string `json:"network"`

// Voting indicates whether a node participated in the current upgrade voting process.
Voting bool `json:"voting"`
// Consensus upgrade voting related fields
UpgradeVoteRounds int `json:"upgradeVoteRounds"`
UpgradeYesVotes int `json:"upgradeYesVotes"`
UpgradeNoVotes int `json:"upgradeNoVotes"`
UpgradeVotes int `json:"upgradeVotes"`
UpgradeVotesRequired int `json:"upgradeVotesRequired"`
NextVersionRound int `json:"nextVersionRound"`

// NeedsUpdate indicates whether the system requires an update based on the current version and available release data.
NeedsUpdate bool `json:"needsUpdate"`
Expand Down Expand Up @@ -77,8 +82,23 @@ func (s Status) Update(status Status) Status {
if s.Network != status.Network {
s.Network = status.Network
}
if s.Voting != status.Voting {
s.Voting = status.Voting
if s.UpgradeVoteRounds != status.UpgradeVoteRounds {
s.UpgradeVoteRounds = status.UpgradeVoteRounds
}
if s.UpgradeYesVotes != status.UpgradeYesVotes {
s.UpgradeYesVotes = status.UpgradeYesVotes
}
if s.UpgradeNoVotes != status.UpgradeNoVotes {
s.UpgradeNoVotes = status.UpgradeNoVotes
}
if s.UpgradeVotes != status.UpgradeVotes {
s.UpgradeVotes = status.UpgradeVotes
}
if s.UpgradeVotesRequired != status.UpgradeVotesRequired {
s.UpgradeVotesRequired = status.UpgradeVotesRequired
}
if s.NextVersionRound != status.NextVersionRound {
s.NextVersionRound = status.NextVersionRound
}
if s.NeedsUpdate != status.NeedsUpdate {
s.NeedsUpdate = status.NeedsUpdate
Expand Down Expand Up @@ -127,9 +147,22 @@ func (s Status) Merge(res api.StatusLike) Status {
s.State = StableState
}

if res.UpgradeNodeVote != nil {
s.Voting = true
if res.UpgradeNextProtocolVoteBefore != nil {
s.UpgradeVoteRounds = *res.UpgradeVoteRounds
s.UpgradeYesVotes = *res.UpgradeYesVotes
s.UpgradeNoVotes = *res.UpgradeNoVotes
s.UpgradeVotes = *res.UpgradeVotes
s.UpgradeVotesRequired = *res.UpgradeVotesRequired
s.NextVersionRound = res.NextVersionRound
} else {
s.UpgradeVoteRounds = 0
s.UpgradeYesVotes = 0
s.UpgradeNoVotes = 0
s.UpgradeVotes = 0
s.UpgradeVotesRequired = 0
s.NextVersionRound = 0
}

return s
}

Expand Down
19 changes: 12 additions & 7 deletions ui/internal/test/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,26 @@ package test

import (
"context"
"time"

"github.com/algorandfoundation/nodekit/api"
"github.com/algorandfoundation/nodekit/internal/algod"
mock2 "github.com/algorandfoundation/nodekit/internal/test/mock"
"time"
)

func GetState(client api.ClientWithResponsesInterface) *algod.StateModel {
sm := &algod.StateModel{
Status: algod.Status{
State: algod.StableState,
Version: "v-test",
Network: "v-test-network",
Voting: false,
NeedsUpdate: false,
LastRound: 0,
State: algod.StableState,
Version: "v-test",
Network: "v-test-network",
UpgradeVoteRounds: 0,
UpgradeYesVotes: 0,
UpgradeNoVotes: 0,
UpgradeVotes: 0,
UpgradeVotesRequired: 0,
NeedsUpdate: false,
LastRound: 0,

Client: client,
HttpPkg: new(api.HttpPkg),
Expand Down
5 changes: 3 additions & 2 deletions ui/modals/generate/controller.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package generate

import (
"github.com/algorandfoundation/nodekit/internal/algod"
"github.com/algorandfoundation/nodekit/internal/algod/participation"
"strconv"
"time"

"github.com/algorandfoundation/nodekit/internal/algod"
"github.com/algorandfoundation/nodekit/internal/algod/participation"

"github.com/algorandfoundation/nodekit/ui/app"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/textinput"
Expand Down
5 changes: 3 additions & 2 deletions ui/pages/accounts/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package accounts

import (
"bytes"
"testing"
"time"

"github.com/algorandfoundation/nodekit/internal/algod"
"github.com/algorandfoundation/nodekit/ui/internal/test"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/x/ansi"
"github.com/charmbracelet/x/exp/golden"
"github.com/charmbracelet/x/exp/teatest"
"testing"
"time"
)

func Test_New(t *testing.T) {
Expand Down
78 changes: 74 additions & 4 deletions ui/protocol.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package ui

import (
"fmt"
"strconv"
"strings"
"time"

"github.com/algorandfoundation/nodekit/internal/algod"
"github.com/algorandfoundation/nodekit/ui/style"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"strconv"
"strings"
)

// ProtocolViewModel includes the internal.StatusModel and internal.Model
type ProtocolViewModel struct {
Data algod.Status
Metrics algod.Metrics
TerminalWidth int
TerminalHeight int
IsVisible bool
Expand All @@ -32,9 +36,15 @@ func (m ProtocolViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m ProtocolViewModel) HandleMessage(msg tea.Msg) (ProtocolViewModel, tea.Cmd) {
switch msg := msg.(type) {
// Handle a Status Update
case *algod.StateModel:
m.Data = msg.Status
m.Metrics = msg.Metrics
case algod.Status:
m.Data = msg
return m, nil
case algod.Metrics:
m.Metrics = msg
return m, nil
// Update Viewport Size
case tea.WindowSizeMsg:
m.TerminalWidth = msg.Width
Expand All @@ -45,6 +55,66 @@ func (m ProtocolViewModel) HandleMessage(msg tea.Msg) (ProtocolViewModel, tea.Cm
return m, nil
}

func plural(singularForm string, value int) string {
if value == 1 {
return singularForm
} else {
return singularForm + "s"
}
}

func formatScheduledUpgrade(status algod.Status, metrics algod.Metrics) string {
roundDelta := status.NextVersionRound - int(status.LastRound)
eta := time.Duration(roundDelta) * metrics.RoundTime
minutes := int(eta.Minutes()) % 60
hours := int(eta.Hours()) % 24
days := int(eta.Hours()) / 24
str := "Scheduled"
if days > 0 {
str = str + fmt.Sprintf(" %d %s", days, plural("day", days))
}
if hours > 0 {
str = str + fmt.Sprintf(" %d %s", hours, plural("hour", hours))
}
if days == 0 && minutes > 0 {
str = str + fmt.Sprintf(" %d %s", minutes, plural("min", minutes))
}
return str
}

func formatProtocolVote(status algod.Status, metrics algod.Metrics) string {
if status.NextVersionRound > int(status.LastRound)+1 {
return formatScheduledUpgrade(status, metrics)
}

voting := status.UpgradeYesVotes > 0 || status.UpgradeNoVotes > 0
if !voting {
return "No"
}

totalVotesCast := status.UpgradeYesVotes + status.UpgradeNoVotes
percentageProgress := 100 * totalVotesCast / status.UpgradeVoteRounds
percentageYes := 100 * status.UpgradeYesVotes / totalVotesCast

label := "Yes"
percentageVoteDisplay := percentageYes
if percentageYes < 50 {
label = "No"
percentageVoteDisplay = 100 * status.UpgradeNoVotes / totalVotesCast
}
statusString := fmt.Sprintf("Voting %d%% complete, %d%% %s", percentageProgress, percentageVoteDisplay, label)

passing := status.UpgradeYesVotes > status.UpgradeVotesRequired
if passing {
statusString = statusString + ", will pass"
}
failThreshold := status.UpgradeVoteRounds - status.UpgradeVotesRequired
if status.UpgradeNoVotes > failThreshold {
statusString = statusString + ", will fail"
}
return statusString
}

// View renders the view for the ProtocolViewModel according to the current state and dimensions.
func (m ProtocolViewModel) View() string {
if !m.IsVisible {
Expand Down Expand Up @@ -85,8 +155,7 @@ func (m ProtocolViewModel) View() string {
if !isCompact {
rows = append(rows, "")
}
rows = append(rows, style.Blue.Render(" Consensus Upgrade Voting: ")+strconv.FormatBool(m.Data.Voting))

rows = append(rows, style.Blue.Render(" Protocol Upgrade: ")+formatProtocolVote(m.Data, m.Metrics))
if isCompact && m.Data.NeedsUpdate {
rows = append(rows, style.Blue.Render(" Upgrade Available: ")+style.Green.Render(strconv.FormatBool(m.Data.NeedsUpdate)))
}
Expand All @@ -99,6 +168,7 @@ func (m ProtocolViewModel) View() string {
func MakeProtocolViewModel(state *algod.StateModel) ProtocolViewModel {
return ProtocolViewModel{
Data: state.Status,
Metrics: state.Metrics,
TerminalWidth: 0,
TerminalHeight: 0,
IsVisible: true,
Expand Down
Loading

0 comments on commit 3b67f7d

Please sign in to comment.