Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Universal CLI #1662

Merged
merged 61 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
fc00a77
reflect marshaler
containerman17 Sep 27, 2024
09a8367
go mod tidy
containerman17 Sep 27, 2024
ef66086
Merge branch 'main' into reflect-marshaler
containerman17 Sep 28, 2024
ee78c67
nits by ARR4N
containerman17 Sep 30, 2024
f6a144a
lint
containerman17 Sep 30, 2024
6a97f02
Merge branch 'main' into reflect-marshaler
containerman17 Oct 1, 2024
15153e2
some error tests
containerman17 Oct 1, 2024
f350683
Merge branch 'reflect-marshaler' of https://github.com/ava-labs/hyper…
containerman17 Oct 1, 2024
2a28d5a
Merge branch 'main' into reflect-marshaler
containerman17 Oct 8, 2024
e70a7e0
tidy
containerman17 Oct 8, 2024
b67fbae
replace codec.LinearCodec.Unmarshal for UnmarshalFrom
containerman17 Oct 8, 2024
ddb5e6f
format
containerman17 Oct 8, 2024
c4253e7
Merge branch 'main' into reflect-marshaler
containerman17 Oct 10, 2024
cb969a9
nits
containerman17 Oct 10, 2024
d81655d
fix error name
containerman17 Oct 10, 2024
11dd321
working with addresses
containerman17 Oct 14, 2024
48e370e
Merge branch 'main' into cli
containerman17 Oct 14, 2024
1c8581a
ping
containerman17 Oct 14, 2024
ab5d63f
reorganize ping
containerman17 Oct 14, 2024
cb01730
actions
containerman17 Oct 14, 2024
bf3dc64
endpoint command
containerman17 Oct 14, 2024
af035e8
read result
containerman17 Oct 21, 2024
f3f62bf
read result
containerman17 Oct 21, 2024
9c2c631
add support for 0x
containerman17 Oct 21, 2024
36bb8e1
request keys
containerman17 Oct 21, 2024
b3263fb
use hex
containerman17 Oct 21, 2024
4cb50cf
parse int and uint
containerman17 Oct 21, 2024
42ae749
update readme
containerman17 Oct 24, 2024
27a38a3
remove base64
containerman17 Oct 24, 2024
cd15478
update morpheus address
containerman17 Oct 24, 2024
3f0561d
sign tx example
containerman17 Oct 24, 2024
7018f74
sign multi actions
containerman17 Oct 24, 2024
2dd46dc
stuck with txs hanging
containerman17 Oct 24, 2024
adaf68d
readme
containerman17 Oct 24, 2024
e7d292c
Merge branch 'main' into cli
containerman17 Oct 24, 2024
0d9e79c
rename package
containerman17 Oct 24, 2024
6c764e4
update readme
containerman17 Oct 24, 2024
978190a
return uint and linmt
containerman17 Oct 24, 2024
12ccaed
lint
containerman17 Oct 24, 2024
1a073a0
remove extra test
containerman17 Oct 24, 2024
ad6420e
lint
containerman17 Oct 24, 2024
f8cd8e4
Fix code scanning alert no. 114: Incorrect conversion between integer…
containerman17 Oct 24, 2024
135bb9e
lint
containerman17 Oct 24, 2024
2cdcdb8
lint
containerman17 Oct 24, 2024
4656625
simplify Uint function
containerman17 Oct 24, 2024
4510f51
simplify Int
containerman17 Oct 24, 2024
59dcdaa
readme update
containerman17 Oct 24, 2024
20ca2a6
reverse consts
containerman17 Oct 24, 2024
45266e3
lint
containerman17 Oct 24, 2024
fcea895
remove InvalidAddress
containerman17 Oct 24, 2024
4269a1b
bring back the NonExistentAddress test
containerman17 Oct 24, 2024
dfc4900
(optional nit) maybe text instead of human?
containerman17 Oct 25, 2024
5170175
switch from reading the config manually to using cobra/viper to read…
containerman17 Oct 25, 2024
8ad54d5
descriptive variable name
containerman17 Oct 25, 2024
40611e0
unexpected field provided error
containerman17 Oct 25, 2024
c648b25
add TODO for timestamp and max fee
containerman17 Oct 25, 2024
d80ed67
change ws to indexer
containerman17 Oct 25, 2024
1a47159
lint
containerman17 Oct 25, 2024
d100ff0
Merge branch 'main' into cli
containerman17 Oct 25, 2024
0ae0c9a
go mod tidy (viper)
containerman17 Oct 25, 2024
83bd857
move manual tx signing to chain package + nits
aaronbuchwald Oct 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions api/indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func (i *Indexer) storeTransactions(blk *chain.ExecutedBlock) error {
result.Units,
result.Fee,
result.Outputs,
string(result.Error),
); err != nil {
return err
}
Expand All @@ -190,6 +191,7 @@ func (*Indexer) storeTransaction(
units fees.Dimensions,
fee uint64,
outputs [][]byte,
errorStr string,
) error {
outputLength := consts.ByteLen // Single byte containing number of outputs
for _, output := range outputs {
Expand All @@ -206,19 +208,20 @@ func (*Indexer) storeTransaction(
for _, output := range outputs {
writer.PackBytes(output)
}
writer.PackString(errorStr)
if err := writer.Err(); err != nil {
return err
}
return batch.Put(txID[:], writer.Bytes())
}

func (i *Indexer) GetTransaction(txID ids.ID) (bool, int64, bool, fees.Dimensions, uint64, [][]byte, error) {
func (i *Indexer) GetTransaction(txID ids.ID) (bool, int64, bool, fees.Dimensions, uint64, [][]byte, string, error) {
v, err := i.txDB.Get(txID[:])
if errors.Is(err, database.ErrNotFound) {
return false, 0, false, fees.Dimensions{}, 0, nil, nil
return false, 0, false, fees.Dimensions{}, 0, nil, "", nil
}
if err != nil {
return false, 0, false, fees.Dimensions{}, 0, nil, err
return false, 0, false, fees.Dimensions{}, 0, nil, "", err
}
reader := codec.NewReader(v, consts.NetworkSizeLimit)
timestamp := reader.UnpackUint64(true)
Expand All @@ -231,14 +234,15 @@ func (i *Indexer) GetTransaction(txID ids.ID) (bool, int64, bool, fees.Dimension
for i := range outputs {
outputs[i] = reader.UnpackLimitedBytes(consts.NetworkSizeLimit)
}
errorStr := reader.UnpackString(false)
if err := reader.Err(); err != nil {
return false, 0, false, fees.Dimensions{}, 0, nil, err
return false, 0, false, fees.Dimensions{}, 0, nil, "", err
}
dimensions, err := fees.UnpackDimensions(dimensionsBytes)
if err != nil {
return false, 0, false, fees.Dimensions{}, 0, nil, err
return false, 0, false, fees.Dimensions{}, 0, nil, "", err
}
return true, int64(timestamp), success, dimensions, fee, outputs, nil
return true, int64(timestamp), success, dimensions, fee, outputs, errorStr, nil
}

func (i *Indexer) Close() error {
Expand Down
4 changes: 3 additions & 1 deletion api/indexer/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ type GetTxResponse struct {
Units fees.Dimensions `json:"units"`
Fee uint64 `json:"fee"`
Outputs []codec.Bytes `json:"result"`
ErrorStr string `json:"errorStr"`
}

type Server struct {
Expand All @@ -122,7 +123,7 @@ func (s *Server) GetTx(req *http.Request, args *GetTxRequest, reply *GetTxRespon
_, span := s.tracer.Start(req.Context(), "Indexer.GetTx")
defer span.End()

found, t, success, units, fee, outputs, err := s.indexer.GetTransaction(args.TxID)
found, t, success, units, fee, outputs, errorStr, err := s.indexer.GetTransaction(args.TxID)
if err != nil {
return err
}
Expand All @@ -139,5 +140,6 @@ func (s *Server) GetTx(req *http.Request, args *GetTxRequest, reply *GetTxRespon
wrappedOutputs[i] = codec.Bytes(output)
}
reply.Outputs = wrappedOutputs
reply.ErrorStr = errorStr
return nil
}
10 changes: 8 additions & 2 deletions cmd/hypersdk-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ FIXME: Has to point to the commit with the latest update from main, or just `@ma

## Configuration

The CLI stores configuration in `~/.hypersdk-cli/config.cfg`. This includes:
The CLI stores configuration in `~/.hypersdk-cli/config.yaml`. This includes:
- Private key
- Endpoint URL

Example setup for a local HyperSDK VM:
```bash
hypersdk-cli endpoint set --endpoint=http://localhost:9650/ext/bc/morpheusvm/
hypersdk-cli key set --key=0x323b1d8f4eed5f0da9da93071b034f2dce9d2d22692c172f3cb252a64ddfafd01b057de320297c29ad0c1f589ea216869cf1938d88c9fbd70d6748323dbf2fa7
```

## Global Flags

- `--endpoint`: Override the default endpoint for a single command
- `-o, --output`: Set output format (`human` or `json`)
- `-o, --output`: Set output format (`text` or `json`)

## Commands

Expand Down
2 changes: 1 addition & 1 deletion cmd/hypersdk-cli/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var actionsCmd = &cobra.Command{
Use: "actions",
Short: "Print the list of actions available in the ABI",
RunE: func(cmd *cobra.Command, _ []string) error {
endpoint, err := getConfigValue(cmd, "endpoint")
endpoint, err := getConfigValue(cmd, "endpoint", true)
if err != nil {
return fmt.Errorf("failed to get endpoint: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/hypersdk-cli/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var addressCmd = &cobra.Command{
Use: "address",
Short: "Print current key address",
RunE: func(cmd *cobra.Command, _ []string) error {
keyString, err := getConfigValue(cmd, "key")
keyString, err := getConfigValue(cmd, "key", true)
if err != nil {
return fmt.Errorf("failed to get key: %w", err)
}
Expand Down
124 changes: 52 additions & 72 deletions cmd/hypersdk-cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,49 @@ import (
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/ava-labs/hypersdk/codec"
)

func init() {
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Fprintln(os.Stderr, "Error getting home directory:", err)
os.Exit(1)
}

configDir := filepath.Join(homeDir, ".hypersdk-cli")
if err := os.MkdirAll(configDir, 0o755); err != nil {
fmt.Fprintln(os.Stderr, "Error creating config directory:", err)
os.Exit(1)
}

configFile := filepath.Join(configDir, "config.yaml")
if _, err := os.Stat(configFile); os.IsNotExist(err) {
if _, err := os.Create(configFile); err != nil {
fmt.Fprintln(os.Stderr, "Error creating config file:", err)
os.Exit(1)
}
}

// Set config name and paths
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(configDir)

// Read config
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
fmt.Fprintln(os.Stderr, "Error reading config:", err)
os.Exit(1)
}
// Config file not found; will be created when needed
}
}

func isJSONOutputRequested(cmd *cobra.Command) (bool, error) {
output, err := getConfigValue(cmd, "output")
output, err := getConfigValue(cmd, "output", false)
if err != nil {
return false, fmt.Errorf("failed to get output format: %w", err)
}
Expand All @@ -43,92 +80,35 @@ func printValue(cmd *cobra.Command, v fmt.Stringer) error {
}
}

func getConfigValue(cmd *cobra.Command, name string) (string, error) {
// Check if the value is among flags
if value, err := cmd.Flags().GetString(name); err == nil && value != "" {
func getConfigValue(cmd *cobra.Command, key string, required bool) (string, error) {
// Check flags first
if value, err := cmd.Flags().GetString(key); err == nil && value != "" {
return value, nil
}

// If not in flags, check the config file
config, err := readConfig()
if err != nil {
return "", fmt.Errorf("failed to read config: %w", err)
}

if value, ok := config[name]; ok {
// Then check viper
if value := viper.GetString(key); value != "" {
return value, nil
}

return "", fmt.Errorf("value for %s not found", name)
}

func updateConfig(name, value string) error {
config, err := readConfig()
if err != nil {
return fmt.Errorf("failed to read config: %w", err)
if required {
return "", fmt.Errorf("required value for %s not found", key)
}

config[name] = value
return writeConfig(config)
return "", nil
}

func readConfig() (map[string]string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get home directory: %w", err)
}

configPath := filepath.Join(homeDir, ".hypersdk-cli", "config.cfg")
data, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
return make(map[string]string), nil
}
return nil, fmt.Errorf("failed to read config file: %w", err)
}

config := make(map[string]string)
lines := strings.Split(string(data), "\n")
for _, line := range lines {
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
config[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}

return config, nil
}

func writeConfig(config map[string]string) error {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home directory: %w", err)
}

configDir := filepath.Join(homeDir, ".hypersdk-cli")
if err := os.MkdirAll(configDir, 0o755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}

configPath := filepath.Join(configDir, "config.cfg")
var buf strings.Builder
for key, value := range config {
buf.WriteString(fmt.Sprintf("%s = %s\n", key, value))
}

if err := os.WriteFile(configPath, []byte(buf.String()), 0o600); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}

return nil
func setConfigValue(key, value string) error {
viper.Set(key, value)
return viper.WriteConfig()
}

func decodeFileOrHex(whatever string) ([]byte, error) {
if decoded, err := codec.LoadHex(whatever, -1); err == nil {
func decodeFileOrHex(fileNameOrHex string) ([]byte, error) {
if decoded, err := codec.LoadHex(fileNameOrHex, -1); err == nil {
return decoded, nil
}

if fileContents, err := os.ReadFile(whatever); err == nil {
if fileContents, err := os.ReadFile(fileNameOrHex); err == nil {
return fileContents, nil
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/hypersdk-cli/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var endpointCmd = &cobra.Command{
Use: "endpoint",
Short: "Manage endpoint",
RunE: func(cmd *cobra.Command, _ []string) error {
endpoint, err := getConfigValue(cmd, "endpoint")
endpoint, err := getConfigValue(cmd, "endpoint", true)
if err != nil {
return fmt.Errorf("failed to get endpoint: %w", err)
}
Expand Down
20 changes: 2 additions & 18 deletions cmd/hypersdk-cli/endpoint_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@
package main

import (
"context"
"errors"
"fmt"
"log"

"github.com/spf13/cobra"

"github.com/ava-labs/hypersdk/api/jsonrpc"
)

var endpointSetCmd = &cobra.Command{
Expand All @@ -27,35 +24,22 @@ var endpointSetCmd = &cobra.Command{
return errors.New("endpoint is required")
}

if err := updateConfig("endpoint", endpoint); err != nil {
if err := setConfigValue("endpoint", endpoint); err != nil {
return fmt.Errorf("failed to update config: %w", err)
}

client := jsonrpc.NewJSONRPCClient(endpoint)
success, err := client.Ping(context.Background())
pingErr := ""
if err != nil {
pingErr = err.Error()
}

return printValue(cmd, endpointSetCmdResponse{
Endpoint: endpoint,
pingResponse: pingResponse{
PingSucceed: success,
PingError: pingErr,
},
})
},
}

type endpointSetCmdResponse struct {
pingResponse
Endpoint string `json:"endpoint"`
}

func (r endpointSetCmdResponse) String() string {
pingStatus := r.pingResponse.String()
return fmt.Sprintf("Endpoint set to: %s\n%s", r.Endpoint, pingStatus)
return "Endpoint set to: " + r.Endpoint
}

func init() {
Expand Down
14 changes: 10 additions & 4 deletions cmd/hypersdk-cli/key_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"log"

"github.com/spf13/cobra"

Expand All @@ -31,10 +32,9 @@ var keySetCmd = &cobra.Command{
Use: "set",
Short: "Set the private ED25519 key",
RunE: func(cmd *cobra.Command, _ []string) error {
// read directly from the flag instead of calling getConfigValue
keyString, err := cmd.Flags().GetString("key")
if err != nil {
return fmt.Errorf("failed to get key: %w", err)
return fmt.Errorf("failed to get key flag: %w", err)
}
if keyString == "" {
return errors.New("--key is required")
Expand All @@ -50,8 +50,8 @@ func checkAndSavePrivateKey(keyString string, cmd *cobra.Command) error {
return fmt.Errorf("failed to decode key: %w", err)
}

err = updateConfig("key", hex.EncodeToString(key[:]))
if err != nil {
// Use Viper to save the key
if err := setConfigValue("key", hex.EncodeToString(key[:])); err != nil {
return fmt.Errorf("failed to update config: %w", err)
}

Expand Down Expand Up @@ -88,4 +88,10 @@ func (r keySetCmdResponse) String() string {

func init() {
keyCmd.AddCommand(keySetCmd, keyGenerateCmd)
keySetCmd.Flags().String("key", "", "Private key in hex format or path to file containing the key")

err := keySetCmd.MarkFlagRequired("key")
if err != nil {
log.Fatalf("failed to mark key flag as required: %s", err)
}
}
2 changes: 1 addition & 1 deletion cmd/hypersdk-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func Execute() {
}

func init() {
rootCmd.PersistentFlags().StringP("output", "o", "human", "Output format (human or json)")
rootCmd.PersistentFlags().StringP("output", "o", "text", "Output format (text or json)")
rootCmd.PersistentFlags().String("endpoint", "", "Override the default endpoint")
rootCmd.PersistentFlags().String("key", "", "Private ED25519 key as hex string")
}
Expand Down
Loading
Loading