Skip to content

Commit

Permalink
chore: create fp from json (#207)
Browse files Browse the repository at this point in the history
Alternative to passing in flags, the operator would provide a JSON file.

[Closes](#14)
  • Loading branch information
Lazar955 authored Dec 10, 2024
1 parent 69c755f commit 768bec3
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 70 deletions.
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

0 comments on commit 768bec3

Please sign in to comment.