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

chore: create fp from json #207

Merged
merged 6 commits into from
Dec 10, 2024
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## Unreleased

### Improvements

* [#207](https://github.com/babylonlabs-io/finality-provider/pull/207) create finality provider from JSON file

## v0.13.1

### Bug Fixes
Expand Down
279 changes: 212 additions & 67 deletions finality-provider/cmd/fpd/daemon/daemon_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"

"cosmossdk.io/math"
"github.com/babylonlabs-io/babylon/types"
Expand Down Expand Up @@ -74,9 +76,32 @@ func CommandCreateFP() *cobra.Command {
Short: "Create a finality provider object and save it in database.",
Long: "Create a new finality provider object and store it in the finality provider database. " +
"It needs to have an operating EOTS manager available and running.",
Example: fmt.Sprintf(`fpd create-finality-provider --daemon-address %s ...`, defaultFpdDaemonAddress),
Args: cobra.NoArgs,
RunE: fpcmd.RunEWithClientCtx(runCommandCreateFP),
Example: strings.TrimSpace(
fmt.Sprintf(`
Either by specifying all flags manually:

$fpd create-finality-provider --daemon-address %s ...

Or providing the path to finality-provider.json:
$fpd create-finality-provider --daemon-address %s --from-file /path/to/finality-provider.json

Where finality-provider.json contains:

{
"keyName": "The unique key name of the finality provider's Babylon account",
"chainID": "The identifier of the consumer chain",
"passphrase": "The pass phrase used to encrypt the keys",
"commissionRate": "The commission rate for the finality provider, e.g., 0.05"",
"moniker": ""A human-readable name for the finality provider",
"identity": "A optional identity signature",
"website": "Validator's (optional) website",
"securityContract": "Validator's (optional) security contact email",
"details": "Validator's (optional) details",
"eotsPK": "The hex string of the finality provider's EOTS public key"
}
`, defaultFpdDaemonAddress, defaultFpdDaemonAddress)),
Args: cobra.NoArgs,
RunE: fpcmd.RunEWithClientCtx(runCommandCreateFP),
}

f := cmd.Flags()
Expand All @@ -92,55 +117,58 @@ func CommandCreateFP() *cobra.Command {
f.String(securityContactFlag, "", "An email for security contact")
f.String(detailsFlag, "", "Other optional details")
f.String(fpEotsPkFlag, "", "The hex string of the finality provider's EOTS public key")

// make flags required
if err := cmd.MarkFlagRequired(chainIDFlag); err != nil {
panic(err)
}
if err := cmd.MarkFlagRequired(keyNameFlag); err != nil {
panic(err)
}
if err := cmd.MarkFlagRequired(monikerFlag); err != nil {
panic(err)
}
if err := cmd.MarkFlagRequired(commissionRateFlag); err != nil {
panic(err)
}
if err := cmd.MarkFlagRequired(fpEotsPkFlag); err != nil {
panic(err)
f.String(fromFile, "", "Path to a json file containing finality provider data")

cmd.PreRunE = func(cmd *cobra.Command, _ []string) error {
fromFilePath, _ := cmd.Flags().GetString(fromFile)
if fromFilePath == "" {
// Mark flags as required only if --from-file is not provided
if err := cmd.MarkFlagRequired(chainIDFlag); err != nil {
return err
}
if err := cmd.MarkFlagRequired(keyNameFlag); err != nil {
return err
}
if err := cmd.MarkFlagRequired(monikerFlag); err != nil {
return err
}
if err := cmd.MarkFlagRequired(commissionRateFlag); err != nil {
return err
}
if err := cmd.MarkFlagRequired(fpEotsPkFlag); err != nil {
return err
}
}
return nil
}

return cmd
}

func runCommandCreateFP(ctx client.Context, cmd *cobra.Command, _ []string) error {
flags := cmd.Flags()
daemonAddress, err := flags.GetString(fpdDaemonAddressFlag)
if err != nil {
return fmt.Errorf("failed to read flag %s: %w", fpdDaemonAddressFlag, err)
}

commissionRateStr, err := flags.GetString(commissionRateFlag)
if err != nil {
return fmt.Errorf("failed to read flag %s: %w", commissionRateFlag, err)
}
commissionRate, err := math.LegacyNewDecFromStr(commissionRateStr)
fpJSONPath, err := flags.GetString(fromFile)
if err != nil {
return fmt.Errorf("invalid commission rate: %w", err)
return fmt.Errorf("failed to read flag %s: %w", fromFile, err)
}

description, err := getDescriptionFromFlags(flags)
if err != nil {
return fmt.Errorf("invalid description: %w", err)
var fp *parsedFinalityProvider
if fpJSONPath != "" {
fp, err = parseFinalityProviderJSON(fpJSONPath, ctx.HomeDir)
if err != nil {
panic(err)
}
} else {
fp, err = parseFinalityProviderFlags(cmd, ctx.HomeDir)
if err != nil {
panic(err)
}
}

keyName, err := loadKeyName(ctx.HomeDir, cmd)
daemonAddress, err := flags.GetString(fpdDaemonAddressFlag)
if err != nil {
return fmt.Errorf("not able to load key name: %w", err)
}

if keyName == "" {
return fmt.Errorf("keyname cannot be empty")
return fmt.Errorf("failed to read flag %s: %w", fpdDaemonAddressFlag, err)
}

client, cleanUp, err := dc.NewFinalityProviderServiceGRpcClient(daemonAddress)
Expand All @@ -153,37 +181,14 @@ func runCommandCreateFP(ctx client.Context, cmd *cobra.Command, _ []string) erro
}
}()

chainID, err := flags.GetString(chainIDFlag)
if err != nil {
return fmt.Errorf("failed to read flag %s: %w", chainIDFlag, err)
}

if chainID == "" {
return fmt.Errorf("chain-id cannot be empty")
}

passphrase, err := flags.GetString(passphraseFlag)
if err != nil {
return fmt.Errorf("failed to read flag %s: %w", passphraseFlag, err)
}

eotsPkHex, err := flags.GetString(fpEotsPkFlag)
if err != nil {
return fmt.Errorf("failed to read flag %s: %w", fpEotsPkFlag, err)
}

if eotsPkHex == "" {
return fmt.Errorf("eots-pk cannot be empty")
}

res, err := client.CreateFinalityProvider(
context.Background(),
keyName,
chainID,
eotsPkHex,
passphrase,
description,
&commissionRate,
fp.keyName,
fp.chainID,
fp.eotsPK,
fp.passphrase,
fp.description,
&fp.commissionRate,
)
if err != nil {
return err
Expand Down Expand Up @@ -516,3 +521,143 @@ func loadKeyName(homeDir string, cmd *cobra.Command) (string, error) {
}
return keyName, nil
}

type parsedFinalityProvider struct {
keyName string
chainID string
eotsPK string
passphrase string
description stakingtypes.Description
commissionRate math.LegacyDec
}

func parseFinalityProviderJSON(path string, homeDir string) (*parsedFinalityProvider, error) {
type internalFpJSON struct {
KeyName string `json:"keyName"`
ChainID string `json:"chainID"`
Passphrase string `json:"passphrase"`
CommissionRate string `json:"commissionRate"`
Moniker string `json:"moniker"`
Identity string `json:"identity"`
Website string `json:"website"`
SecurityContract string `json:"securityContract"`
Details string `json:"details"`
EotsPK string `json:"eotsPK"`
}

// #nosec G304 - The log file path is provided by the user and not externally
contents, err := os.ReadFile(path)
if err != nil {
return nil, err
}

var fp internalFpJSON
if err := json.Unmarshal(contents, &fp); err != nil {
return nil, err
}

if fp.ChainID == "" {
return nil, fmt.Errorf("chainID is required")
}

if fp.KeyName == "" {
cfg, err := fpcfg.LoadConfig(homeDir)
if err != nil {
return nil, fmt.Errorf("failed to load config from %s: %w", fpcfg.CfgFile(homeDir), err)
}
fp.KeyName = cfg.BabylonConfig.Key
if fp.KeyName == "" {
return nil, fmt.Errorf("the key is neither in config nor provided in the json file")
}
}

if fp.Moniker == "" {
return nil, fmt.Errorf("moniker is required")
}

if fp.CommissionRate == "" {
return nil, fmt.Errorf("commissionRate is required")
}

if fp.EotsPK == "" {
return nil, fmt.Errorf("eotsPK is required")
}

commissionRate, err := math.LegacyNewDecFromStr(fp.CommissionRate)
if err != nil {
return nil, fmt.Errorf("invalid commission rate: %w", err)
}

description, err := stakingtypes.NewDescription(fp.Moniker, fp.Identity, fp.Website, fp.SecurityContract, fp.Details).EnsureLength()
if err != nil {
return nil, err
}

return &parsedFinalityProvider{
keyName: fp.KeyName,
chainID: fp.ChainID,
eotsPK: fp.EotsPK,
passphrase: fp.Passphrase,
description: description,
commissionRate: commissionRate,
}, nil
}

func parseFinalityProviderFlags(cmd *cobra.Command, homeDir string) (*parsedFinalityProvider, error) {
flags := cmd.Flags()

commissionRateStr, err := flags.GetString(commissionRateFlag)
if err != nil {
return nil, fmt.Errorf("failed to read flag %s: %w", commissionRateFlag, err)
}
commissionRate, err := math.LegacyNewDecFromStr(commissionRateStr)
if err != nil {
return nil, fmt.Errorf("invalid commission rate: %w", err)
}

description, err := getDescriptionFromFlags(flags)
if err != nil {
return nil, fmt.Errorf("invalid description: %w", err)
}

keyName, err := loadKeyName(homeDir, cmd)
if err != nil {
return nil, fmt.Errorf("not able to load key name: %w", err)
}

if keyName == "" {
return nil, fmt.Errorf("keyname cannot be empty")
}

chainID, err := flags.GetString(chainIDFlag)
if err != nil {
return nil, fmt.Errorf("failed to read flag %s: %w", chainIDFlag, err)
}

if chainID == "" {
return nil, fmt.Errorf("chain-id cannot be empty")
}

passphrase, err := flags.GetString(passphraseFlag)
if err != nil {
return nil, fmt.Errorf("failed to read flag %s: %w", passphraseFlag, err)
}

eotsPkHex, err := flags.GetString(fpEotsPkFlag)
if err != nil {
return nil, fmt.Errorf("failed to read flag %s: %w", fpEotsPkFlag, err)
}

if eotsPkHex == "" {
return nil, fmt.Errorf("eots-pk cannot be empty")
}

return &parsedFinalityProvider{
keyName: keyName,
chainID: chainID,
eotsPK: eotsPkHex,
passphrase: passphrase,
description: description,
commissionRate: commissionRate,
}, nil
}
1 change: 1 addition & 0 deletions finality-provider/cmd/fpd/daemon/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
chainIDFlag = "chain-id"
signedFlag = "signed"
checkDoubleSignFlag = "check-double-sign"
fromFile = "from-file"

// flags for description
monikerFlag = "moniker"
Expand Down
Loading
Loading