Skip to content

Commit

Permalink
Merge pull request #19 from algorandfoundation/fix/fixup-demo
Browse files Browse the repository at this point in the history
refactor: style, borders, and adds more account state
  • Loading branch information
HashMapsData2Value authored Oct 30, 2024
2 parents 0f0dc2c + 1d15812 commit 96dbae0
Show file tree
Hide file tree
Showing 32 changed files with 415 additions and 309 deletions.
2 changes: 1 addition & 1 deletion api/lf.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/algorandfoundation/hack-tui/api"
"github.com/algorandfoundation/hack-tui/internal"
"github.com/algorandfoundation/hack-tui/ui"
"github.com/algorandfoundation/hack-tui/ui/style"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/log"
"github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider"
Expand All @@ -32,7 +33,7 @@ var (
rootCmd = &cobra.Command{
Use: "algorun",
Short: "Manage Algorand nodes",
Long: ui.Purple(BANNER) + "\n",
Long: style.Purple(BANNER) + "\n",
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
},
Expand Down Expand Up @@ -61,7 +62,7 @@ var (
},
ParticipationKeys: partkeys,
}
state.Accounts = internal.AccountsFromState(&state, client)
state.Accounts = internal.AccountsFromState(&state, new(internal.Clock), client)

// Fetch current state
err = state.Status.Fetch(context.Background(), client)
Expand All @@ -73,6 +74,7 @@ var (
p := tea.NewProgram(
m,
tea.WithAltScreen(),
tea.WithFPS(120),
)
go func() {
state.Watch(func(status *internal.StateModel, err error) {
Expand Down Expand Up @@ -112,19 +114,19 @@ func init() {
rootCmd.Version = Version

// Bindings
rootCmd.PersistentFlags().StringVar(&server, "server", "", ui.LightBlue("server address"))
rootCmd.PersistentFlags().StringVar(&token, "token", "", ui.LightBlue("server token"))
rootCmd.PersistentFlags().StringVar(&server, "server", "", style.LightBlue("server address"))
rootCmd.PersistentFlags().StringVar(&token, "token", "", style.LightBlue("server token"))
_ = viper.BindPFlag("server", rootCmd.PersistentFlags().Lookup("server"))
_ = viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))

// Update Long Text
rootCmd.Long +=
ui.Magenta("Configuration: ") + viper.GetViper().ConfigFileUsed() + "\n" +
ui.LightBlue("Server: ") + viper.GetString("server")
style.Magenta("Configuration: ") + viper.GetViper().ConfigFileUsed() + "\n" +
style.LightBlue("Server: ") + viper.GetString("server")

if viper.GetString("data") != "" {
rootCmd.Long +=
ui.Magenta("\nAlgorand Data: ") + viper.GetString("data")
style.Magenta("\nAlgorand Data: ") + viper.GetString("data")
}

// Add Commands
Expand Down
5 changes: 3 additions & 2 deletions cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"github.com/algorandfoundation/hack-tui/internal"
"github.com/algorandfoundation/hack-tui/ui"
"github.com/algorandfoundation/hack-tui/ui/style"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -16,10 +17,10 @@ import (
var statusCmd = &cobra.Command{
Use: "status",
Short: "Get the node status",
Long: ui.Purple(BANNER) + "\n" + ui.LightBlue("View the node status"),
Long: style.Purple(BANNER) + "\n" + style.LightBlue("View the node status"),
RunE: func(cmd *cobra.Command, args []string) error {
if viper.GetString("server") == "" {
return errors.New(ui.Magenta("server is required"))
return errors.New(style.Magenta("server is required"))
}

// Get Algod from configuration
Expand Down
43 changes: 19 additions & 24 deletions internal/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ type Account struct {
Keys int
// Expires is the date the participation key will expire
Expires time.Time
// The LastModified round, this only pertains to keys that can be updated
LastModified int
}

// Gets the list of addresses created at genesis from the genesis file
Expand Down Expand Up @@ -92,17 +90,8 @@ func getAddressesFromGenesis(client *api.ClientWithResponses) ([]string, string,
return addresses, rewardsPool, feeSink, nil
}

func isValidStatus(status string) bool {
validStatuses := map[string]bool{
"Online": true,
"Offline": true,
"Not Participating": true,
}
return validStatuses[status]
}

// Get Online Status of Account
func getAccountOnlineStatus(client *api.ClientWithResponses, address string) (string, error) {
func GetAccount(client *api.ClientWithResponses, address string) (api.Account, error) {
var format api.AccountInformationParamsFormat = "json"
r, err := client.AccountInformationWithResponse(
context.Background(),
Expand All @@ -111,23 +100,20 @@ func getAccountOnlineStatus(client *api.ClientWithResponses, address string) (st
Format: &format,
})

var accountInfo api.Account
if err != nil {
return "N/A", err
return accountInfo, err
}

if r.StatusCode() != 200 {
return "N/A", errors.New(fmt.Sprintf("Failed to get account information. Received error code: %d", r.StatusCode()))
return accountInfo, errors.New(fmt.Sprintf("Failed to get account information. Received error code: %d", r.StatusCode()))
}

if r.JSON200 == nil {
return "N/A", errors.New("Failed to get account information. JSON200 is nil")
}

return r.JSON200.Status, nil
return *r.JSON200, nil
}

// AccountsFromParticipationKeys maps an array of api.ParticipationKey to a keyed map of Account
func AccountsFromState(state *StateModel, client *api.ClientWithResponses) map[string]Account {
func AccountsFromState(state *StateModel, t Time, client *api.ClientWithResponses) map[string]Account {
values := make(map[string]Account)
if state == nil || state.ParticipationKeys == nil {
return values
Expand All @@ -136,18 +122,27 @@ func AccountsFromState(state *StateModel, client *api.ClientWithResponses) map[s
val, ok := values[key.Address]
if !ok {

statusOnline, err := getAccountOnlineStatus(client, key.Address)
account, err := GetAccount(client, key.Address)

// TODO: handle error
if err != nil {
// TODO: Logging
panic(err)
}

var expires = t.Now()
if key.EffectiveLastValid != nil {
now := t.Now()
roundDiff := max(0, *key.EffectiveLastValid-int(state.Status.LastRound))
distance := int(state.Metrics.RoundTime) * roundDiff
expires = now.Add(time.Duration(distance))
}

values[key.Address] = Account{
Address: key.Address,
Status: statusOnline,
Balance: 0,
Expires: time.Unix(0, 0),
Status: account.Status,
Balance: account.Amount / 1000000,
Expires: expires,
Keys: 1,
}
} else {
Expand Down
148 changes: 105 additions & 43 deletions internal/accounts_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package internal

import (
"testing"
"time"

"github.com/algorandfoundation/hack-tui/api"
"github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider"
"github.com/stretchr/testify/assert"
"testing"
"time"
)

type TestClock struct{}

func (TestClock) Now() time.Time { return time.Time{} }

func Test_AccountsFromState(t *testing.T) {

// Setup elevated client
Expand All @@ -24,67 +27,68 @@ func Test_AccountsFromState(t *testing.T) {
t.Fatal(err)
}

// Test getAccountOnlineStatus

var mapAddressOnlineStatus = make(map[string]string)

var mapAccounts = make(map[string]api.Account)
var onlineAccounts = make([]api.Account, 0)
for _, address := range addresses {
status, err := getAccountOnlineStatus(client, address)
acct, err := GetAccount(client, address)
if err != nil {
t.Fatal(err)
}

assert.True(t, status == "Online" || status == "Offline")
mapAddressOnlineStatus[address] = status
assert.True(t, acct.Status == "Online" || acct.Status == "Offline")
mapAccounts[address] = acct
if acct.Status == "Online" {
onlineAccounts = append(onlineAccounts, acct)
}
}

status, err := getAccountOnlineStatus(client, rewardsPool)
acct, err := GetAccount(client, rewardsPool)
if err != nil {
t.Fatal(err)
}
if status != "Not Participating" {
t.Fatalf("Expected RewardsPool to be 'Not Participating', got %s", status)
if acct.Status != "Not Participating" {
t.Fatalf("Expected RewardsPool to be 'Not Participating', got %s", acct.Status)
}

status, err = getAccountOnlineStatus(client, feeSink)
acct, err = GetAccount(client, feeSink)
if err != nil {
t.Fatal(err)
}
if status != "Not Participating" {
t.Fatalf("Expected FeeSink to be 'Not Participating', got %s", status)
if acct.Status != "Not Participating" {
t.Fatalf("Expected FeeSink to be 'Not Participating', got %s", acct.Status)
}

_, err = getAccountOnlineStatus(client, "invalid_address")
_, err = GetAccount(client, "invalid_address")
if err == nil {
t.Fatal("Expected error for invalid address")
}

// Test AccountFromState
// Test Account from State

// Prepare expected results
// Only include addresses with "Online" status
onlineAddresses := make(map[string]string)
for address, status := range mapAddressOnlineStatus {
if status == "Online" {
onlineAddresses[address] = status
}
}
effectiveFirstValid := 0
effectiveLastValid := 10000

// Create expectedAccounts dynamically from Online accounts, and mocked participation keys
mockedPartKeys := make([]api.ParticipationKey, 0)
expectedAccounts := make(map[string]Account)
for address, status := range onlineAddresses {
expectedAccounts[address] = Account{
Address: address,
Status: status,
Balance: 0,
Expires: time.Unix(0, 0),
Keys: 1,
LastModified: 0,
}

mockedPartKeys = append(mockedPartKeys, api.ParticipationKey{
Address: address,
// Create mockedPart Keys
var mockedPartKeys = []api.ParticipationKey{
{
Address: onlineAccounts[0].Address,
EffectiveFirstValid: &effectiveFirstValid,
EffectiveLastValid: &effectiveLastValid,
Id: "",
Key: api.AccountParticipation{
SelectionParticipationKey: nil,
StateProofKey: nil,
VoteParticipationKey: nil,
VoteFirstValid: 0,
VoteLastValid: 9999999,
VoteKeyDilution: 0,
},
LastBlockProposal: nil,
LastStateProof: nil,
LastVote: nil,
},
{
Address: onlineAccounts[0].Address,
EffectiveFirstValid: nil,
EffectiveLastValid: nil,
Id: "",
Expand All @@ -99,16 +103,74 @@ func Test_AccountsFromState(t *testing.T) {
LastBlockProposal: nil,
LastStateProof: nil,
LastVote: nil,
})
},
{
Address: onlineAccounts[1].Address,
EffectiveFirstValid: &effectiveFirstValid,
EffectiveLastValid: &effectiveLastValid,
Id: "",
Key: api.AccountParticipation{
SelectionParticipationKey: nil,
StateProofKey: nil,
VoteParticipationKey: nil,
VoteFirstValid: 0,
VoteLastValid: 9999999,
VoteKeyDilution: 0,
},
LastBlockProposal: nil,
LastStateProof: nil,
LastVote: nil,
},
}

// Mock StateModel
state := &StateModel{
Metrics: MetricsModel{
Enabled: true,
Window: 100,
RoundTime: time.Duration(2) * time.Second,
TPS: 20,
RX: 1024,
TX: 2048,
},
Status: StatusModel{
State: "WATCHING",
Version: "v0.0.0-test",
Network: "tuinet",
Voting: false,
NeedsUpdate: false,
LastRound: 1337,
},
ParticipationKeys: &mockedPartKeys,
}

// Calculate expiration
clock := new(TestClock)
now := clock.Now()
roundDiff := max(0, effectiveLastValid-int(state.Status.LastRound))
distance := int(state.Metrics.RoundTime) * roundDiff
expires := now.Add(time.Duration(distance))

// Construct expected accounts
expectedAccounts := map[string]Account{
onlineAccounts[0].Address: {
Address: onlineAccounts[0].Address,
Status: onlineAccounts[0].Status,
Balance: onlineAccounts[0].Amount / 1_000_000,
Keys: 2,
Expires: expires,
},
onlineAccounts[1].Address: {
Address: onlineAccounts[1].Address,
Status: onlineAccounts[1].Status,
Balance: onlineAccounts[1].Amount / 1_000_000,
Keys: 1,
Expires: expires,
},
}

// Call AccountsFromState
accounts := AccountsFromState(state, client)
accounts := AccountsFromState(state, clock, client)

// Assert results
assert.Equal(t, expectedAccounts, accounts)
Expand Down
Loading

0 comments on commit 96dbae0

Please sign in to comment.