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

feat: revamp mnemonic and key file logic #123

Merged
merged 7 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 20 additions & 37 deletions cmd/opinit_bots.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/initia-labs/weave/common"
weavecontext "github.com/initia-labs/weave/context"
"github.com/initia-labs/weave/cosmosutils"
"github.com/initia-labs/weave/io"
weaveio "github.com/initia-labs/weave/io"
"github.com/initia-labs/weave/models/opinit_bots"
"github.com/initia-labs/weave/service"
)
Expand Down Expand Up @@ -136,27 +136,6 @@ func OPInitBotsKeysSetupCommand() *cobra.Command {
return setupCmd
}

func generateKeyFile(keyPath string, botName string) (opinit_bots.KeyFile, error) {
keyFile, err := opinit_bots.GenerateMnemonicKeyfile(botName)
if err != nil {
return keyFile, err
}

// Marshal KeyFile to JSON
data, err := json.MarshalIndent(keyFile, "", " ")
if err != nil {
return keyFile, fmt.Errorf("error marshaling KeyFile to JSON: %w", err)
}

// Write JSON data to a file
err = os.WriteFile(keyPath, data, 0644)
if err != nil {
return keyFile, fmt.Errorf("error writing to file: %w", err)
}

return keyFile, nil
}

func validateConfigFlags(args []string, configPath, keyFilePath string, isGenerateKeyFile bool) error {
if configPath != "" {
if len(args) == 0 {
Expand All @@ -168,7 +147,7 @@ func validateConfigFlags(args []string, configPath, keyFilePath string, isGenera
if keyFilePath == "" && !isGenerateKeyFile {
return fmt.Errorf("invalid configuration: if --with-config is set, either --generate-key-file or --key-file must be provided")
}
if !io.FileOrFolderExists(configPath) {
if !weaveio.FileOrFolderExists(configPath) {
return fmt.Errorf("the provided --with-config does not exist: %s", configPath)
}
} else {
Expand All @@ -192,16 +171,20 @@ func handleWithConfig(cmd *cobra.Command, userHome, opInitHome, configPath, keyF
return err
}

var keyFile opinit_bots.KeyFile
var keyFile *weaveio.KeyFile
if isGenerateKeyFile {
keyPath := filepath.Join(userHome, common.WeaveDataDirectory, fmt.Sprintf("%s.%s.keyfile", common.OpinitGeneratedKeyFilename, botName))
keyFile, err = generateKeyFile(keyPath, botName)
keyFile, err = opinit_bots.GenerateMnemonicKeyfile(botName)
if err != nil {
return err
return fmt.Errorf("error generating keyfile: %v", err)
}
err = keyFile.Write(keyPath)
if err != nil {
return fmt.Errorf("error writing to file: %w", err)
}
fmt.Printf("Key file successfully generated. You can find it at: %s\n", keyPath)
} else {
if !io.FileOrFolderExists(keyFilePath) {
if !weaveio.FileOrFolderExists(keyFilePath) {
return fmt.Errorf("key file is missing at path: %s", keyFilePath)
}

Expand All @@ -225,25 +208,25 @@ func handleWithConfig(cmd *cobra.Command, userHome, opInitHome, configPath, keyF
}

// readAndUnmarshalKeyFile read and unmarshal the key file into the KeyFile struct
func readAndUnmarshalKeyFile(keyFilePath string) (opinit_bots.KeyFile, error) {
func readAndUnmarshalKeyFile(keyFilePath string) (*weaveio.KeyFile, error) {
fileData, err := os.ReadFile(keyFilePath)
if err != nil {
return opinit_bots.KeyFile{}, err
return nil, err
}

var keyFile opinit_bots.KeyFile
err = json.Unmarshal(fileData, &keyFile)
keyFile := &weaveio.KeyFile{}
err = json.Unmarshal(fileData, keyFile)
return keyFile, err
}

// handleExistingOpInitHome handle the case where the opInitHome directory exists
func handleExistingOpInitHome(opInitHome string, botName string, force bool) error {
if io.FileOrFolderExists(opInitHome) {
if weaveio.FileOrFolderExists(opInitHome) {
if force {
// delete db
dbPath := filepath.Join(opInitHome, fmt.Sprintf("%s.db", botName))
if io.FileOrFolderExists(dbPath) {
err := io.DeleteDirectory(dbPath)
if weaveio.FileOrFolderExists(dbPath) {
err := weaveio.DeleteDirectory(dbPath)
if err != nil {
return fmt.Errorf("failed to delete %s", dbPath)
}
Expand All @@ -256,7 +239,7 @@ func handleExistingOpInitHome(opInitHome string, botName string, force bool) err
}

// initializeBotWithConfig initialize a bot based on the provided config
func initializeBotWithConfig(cmd *cobra.Command, fileData []byte, keyFile opinit_bots.KeyFile, opInitHome, userHome, botName string) error {
func initializeBotWithConfig(cmd *cobra.Command, fileData []byte, keyFile *weaveio.KeyFile, opInitHome, userHome, botName string) error {
var err error

switch botName {
Expand All @@ -266,14 +249,14 @@ func initializeBotWithConfig(cmd *cobra.Command, fileData []byte, keyFile opinit
if err != nil {
return err
}
err = opinit_bots.InitializeExecutorWithConfig(config, &keyFile, opInitHome, userHome)
err = opinit_bots.InitializeExecutorWithConfig(config, keyFile, opInitHome, userHome)
case "challenger":
var config opinit_bots.ChallengerConfig
err = json.Unmarshal(fileData, &config)
if err != nil {
return err
}
err = opinit_bots.InitializeChallengerWithConfig(config, &keyFile, opInitHome, userHome)
err = opinit_bots.InitializeChallengerWithConfig(config, keyFile, opInitHome, userHome)
}
if err != nil {
return err
Expand Down
9 changes: 6 additions & 3 deletions common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

const (
WeaveDirectory = ".weave"
WeaveConfigFile = WeaveDirectory + "/config.json"
WeaveDataDirectory = WeaveDirectory + "/data"
WeaveLogDirectory = WeaveDirectory + "/log"

Expand All @@ -18,11 +19,13 @@ const (
MinitiaArtifactsConfigJson = "/artifacts/config.json"
MinitiaArtifactsJson = "/artifacts/artifacts.json"

OPinitDirectory = ".opinit"
OPinitAppName = "opinitd"
OPinitDirectory = ".opinit"
OPinitAppName = "opinitd"
OPinitKeyFileJson = "/weave.keys.json"
OpinitGeneratedKeyFilename = "weave.opinit.generated"

HermesHome = ".hermes"
HermesKeysDirectory = HermesHome + "/keys"
HermesKeyFileJson = HermesHome + "/weave.keys.json"
HermesTempMnemonicFilename = "weave.mnemonic"
OpinitGeneratedKeyFilename = "weave.opinit.generated"
)
2 changes: 1 addition & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func InitializeConfig() error {
return fmt.Errorf("failed to get user home directory: %v", err)
}

configPath := filepath.Join(homeDir, common.WeaveDirectory, "config.json")
configPath := filepath.Join(homeDir, common.WeaveConfigFile)
if err := os.MkdirAll(filepath.Dir(configPath), os.ModePerm); err != nil {
return fmt.Errorf("failed to create config directory: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (m *MockedFilesystem) Stat(name string) (os.FileInfo, error) {
func TestInitializeConfig(t *testing.T) {
fs := new(MockedFilesystem)
home := "/mock/home"
configPath := filepath.Join(home, common.WeaveDirectory, "config.json")
configPath := filepath.Join(home, common.WeaveConfigFile)

// Resetting mocks for next test case
fs.Mock = mock.Mock{}
Expand Down
8 changes: 8 additions & 0 deletions context/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,11 @@ func GetOPInitHome(ctx context.Context) (string, error) {
}
return "", fmt.Errorf("cannot cast the OPInitHomeKey value into type string")
}

func GetOPInitKeyFileJson(ctx context.Context) (string, error) {
opInitHome, err := GetOPInitHome(ctx)
if err != nil {
return "", err
}
return filepath.Join(opInitHome, common.OPinitKeyFileJson), nil
}
11 changes: 0 additions & 11 deletions io/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"path/filepath"
"runtime"

"github.com/atotto/clipboard"

"github.com/initia-labs/weave/client"
)

Expand Down Expand Up @@ -154,12 +152,3 @@ func CopyDirectory(src, des string) error {
}
return nil
}

// CopyToClipboard copies the given string to the clipboard.
func CopyToClipboard(text string) error {
// Copy the text to clipboard
if err := clipboard.WriteAll(text); err != nil {
return fmt.Errorf("failed to copy to clipboard: %v", err)
}
return nil
}
50 changes: 50 additions & 0 deletions io/keyfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io

import (
"encoding/json"
"fmt"
"os"
)

type KeyFile map[string]string

func NewKeyFile() *KeyFile {
kf := make(KeyFile)
return &kf
}

func (k *KeyFile) AddMnemonic(name, mnemonic string) {
(*k)[name] = mnemonic
}

func (k *KeyFile) GetMnemonic(name string) string {
return (*k)[name]
}
traviolus marked this conversation as resolved.
Show resolved Hide resolved

func (k *KeyFile) Write(filePath string) error {
data, err := json.MarshalIndent(k, "", " ")
if err != nil {
return fmt.Errorf("error marshaling KeyFile to JSON: %w", err)
}

return os.WriteFile(filePath, data, 0644)
}

// Load tries to load an existing key file into the struct if the file exists
func (k *KeyFile) Load(filePath string) error {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return nil
}

data, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("error reading file: %w", err)
}

err = json.Unmarshal(data, k)
if err != nil {
return fmt.Errorf("error unmarshaling JSON: %w", err)
}

return nil
}
traviolus marked this conversation as resolved.
Show resolved Hide resolved
49 changes: 16 additions & 33 deletions models/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package models
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"

Expand All @@ -13,7 +15,6 @@ import (
"github.com/initia-labs/weave/config"
weavecontext "github.com/initia-labs/weave/context"
"github.com/initia-labs/weave/crypto"
"github.com/initia-labs/weave/io"
"github.com/initia-labs/weave/styles"
"github.com/initia-labs/weave/types"
"github.com/initia-labs/weave/ui"
Expand Down Expand Up @@ -185,28 +186,15 @@ func (m *GenerateGasStationLoading) View() string {

type GasStationMnemonicDisplayInput struct {
ui.TextInput
ui.Clickable
weavecontext.BaseModel
question string
generatedMnemonic string
question string
}

func NewSystemKeysMnemonicDisplayInput(ctx context.Context) *GasStationMnemonicDisplayInput {
state := weavecontext.GetCurrentState[ExistingCheckerState](ctx)
model := &GasStationMnemonicDisplayInput{
TextInput: ui.NewTextInput(true),
Clickable: *ui.NewClickable(
ui.NewClickableItem(
map[bool]string{
true: "Copied! Click to copy again",
false: "Click here to copy",
}, func() error {
return io.CopyToClipboard(state.generatedMnemonic)
},
)),
BaseModel: weavecontext.BaseModel{Ctx: ctx, CannotBack: true},
question: "Please type `continue` to proceed after you have securely stored the mnemonic.",
generatedMnemonic: state.generatedMnemonic,
BaseModel: weavecontext.BaseModel{Ctx: ctx, CannotBack: true},
question: "Type `continue` to proceed.",
}
model.WithPlaceholder("Type `continue` to continue, Ctrl+C to quit.")
model.WithValidatorFn(common.ValidateExactString("continue"))
Expand All @@ -218,24 +206,19 @@ func (m *GasStationMnemonicDisplayInput) GetQuestion() string {
}

func (m *GasStationMnemonicDisplayInput) Init() tea.Cmd {
return m.Clickable.Init()
return nil
}

func (m *GasStationMnemonicDisplayInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if model, cmd, handled := weavecontext.HandleCommonCommands[ExistingCheckerState](m, msg); handled {
return model, cmd
}

err := m.Clickable.ClickableUpdate(msg)
if err != nil {
return m, m.HandlePanic(err)
}

input, cmd, done := m.TextInput.Update(msg)
if done {
state := weavecontext.PushPageAndGetState[ExistingCheckerState](m)
model := NewWeaveAppInitialization(m.Ctx, state.generatedMnemonic)
return model, tea.Batch(model.Init(), m.Clickable.PostUpdate())
return model, model.Init()
}
m.TextInput = input
return m, cmd
Expand All @@ -248,18 +231,18 @@ func (m *GasStationMnemonicDisplayInput) View() string {
m.HandlePanic(fmt.Errorf("failed to convert mnemonic to bech32 address: %w", err))
}

userHome, err := os.UserHomeDir()
if err != nil {
m.HandlePanic(fmt.Errorf("failed to get user home directory: %w", err))
}

var mnemonicText string
mnemonicText += styles.RenderMnemonic("Gas Station", gasStationAddress, m.generatedMnemonic, m.Clickable.ClickableView(0))
mnemonicText += styles.RenderKey("Gas Station", gasStationAddress)

viewText := m.WrapView(InitHeader(state.isFirstTime) + "\n" + state.weave.Render() + "\n" +
return m.WrapView(InitHeader(state.isFirstTime) + "\n" + state.weave.Render() + "\n" +
styles.BoldUnderlineText("Important", styles.Yellow) + "\n" +
styles.Text("Write down these mnemonic phrases and store them in a safe place. \nIt is the only way to recover your system keys.", styles.Yellow) + "\n\n" +
mnemonicText + "\n" + styles.RenderPrompt(m.GetQuestion(), []string{"`continue`"}, styles.Question) + m.TextInput.View())
err = m.Clickable.ClickableUpdatePositions(viewText)
if err != nil {
m.HandlePanic(err)
}
return viewText
styles.Text(fmt.Sprintf("Note that the mnemonic phrase for Gas Station along with other configuration details will be stored in %s. You can revisit them anytime.", filepath.Join(userHome, common.WeaveConfigFile)), styles.Yellow) + "\n\n" +
mnemonicText + styles.RenderPrompt(m.GetQuestion(), []string{"`continue`"}, styles.Question) + m.TextInput.View())
}

type GasStationMnemonicInput struct {
Expand Down
Loading