Skip to content

Commit

Permalink
feat: Add staking flag and config parser
Browse files Browse the repository at this point in the history
  • Loading branch information
red-0ne committed Nov 14, 2023
1 parent a61013d commit c667c9e
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 54 deletions.
160 changes: 160 additions & 0 deletions x/supplier/client/cli/stake_options_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package cli

import (
"encoding/json"
"fmt"
"net/url"
"os"

sdk "github.com/cosmos/cosmos-sdk/types"

sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
)

// parsedStakeConfig is a parsed version of the stake config file
type parsedStakeConfig struct {
stake sdk.Coin
services []*sharedtypes.SupplierServiceConfig
}

// stakeConfig is the structure of the stake config file
type stakeConfig struct {
Stake string `json:"stake"`
Services []stakeService `json:"services"`
}

// stakeService is the structure describing a single service stake entry in the stake config file
type stakeService struct {
ServiceId string `json:"service_id"`
Endpoints []serviceEndpoint `json:"endpoints"`
}

// serviceEndpoint is the structure describing a single service endpoint in the stake config file
type serviceEndpoint struct {
Url string `json:"url"`
RPCType string `json:"rpc_type"`
Config map[string]string `json:"config"`
}

// parseStakeConfig parses the stake config file into a parsedStakeConfig
func parseStakeConfig(configFile string) (*parsedStakeConfig, error) {
// Read the stake config file into memory
configContent, err := os.ReadFile(configFile)
if err != nil {
return nil, err
}

// Unmarshal the stake config file into a stakeConfig
var stakeConfig *stakeConfig
if err := json.Unmarshal(configContent, &stakeConfig); err != nil {
return nil, err
}

// Prepare the parsedStakeConfig
parsedStakeConfig := &parsedStakeConfig{}

// Parse the stake amount and assign it to the parsedStakeConfig
parsedStakeConfig.stake, err = sdk.ParseCoinNormalized(stakeConfig.Stake)
if err != nil {
return nil, err
}

// Prepare the services slice
var services []*sharedtypes.SupplierServiceConfig

// Populate the services slice
for _, svc := range stakeConfig.Services {
// Validate the serviceId
// TODO_TECH_DEBT: This should be validated against some governance state
// defining the network's supported services
if svc.ServiceId == "" {
return nil, fmt.Errorf("invalid serviceId")
}

// Create a supplied service config with the serviceId
service := &sharedtypes.SupplierServiceConfig{
Service: &sharedtypes.Service{Id: svc.ServiceId},
Endpoints: []*sharedtypes.SupplierEndpoint{},
}

// Iterate over the service endpoints and add their parsed representation to the supplied service config
for _, endpoint := range svc.Endpoints {
parsedEndpointEntry, err := parseEndpointEntry(endpoint)
if err != nil {
return nil, err
}
service.Endpoints = append(service.Endpoints, parsedEndpointEntry)
}
services = append(services, service)
}

parsedStakeConfig.services = services

return parsedStakeConfig, nil
}

func parseEndpointEntry(endpoint serviceEndpoint) (*sharedtypes.SupplierEndpoint, error) {
endpointEntry := &sharedtypes.SupplierEndpoint{}

// Endpoint URL
endpointUrl, err := validateEndpointURL(endpoint)
if err != nil {
return nil, err
}
endpointEntry.Url = endpointUrl

// Endpoint config
endpointEntry.Configs = parseEndpointConfigs(endpoint)

// Endpoint RPC type
endpointEntry.RpcType = parseEndpointRPCType(endpoint)

return endpointEntry, nil
}

func validateEndpointURL(endpoint serviceEndpoint) (string, error) {
// Validate the endpoint URL
if _, err := url.Parse(endpoint.Url); err != nil {
return "", err
}

return endpoint.Url, nil
}

func parseEndpointConfigs(endpoint serviceEndpoint) []*sharedtypes.ConfigOption {
// Prepare the endpoint configs slice
endpointConfigs := []*sharedtypes.ConfigOption{}

// If we have an endpoint config entry, parse it into a slice of ConfigOption
if endpoint.Config != nil {
// Iterate over the endpoint config entries and add them to the slice of ConfigOption
for key, value := range endpoint.Config {
var configKey sharedtypes.ConfigOptions

// Make sure the config key is valid
switch key {
case "timeout":
configKey = sharedtypes.ConfigOptions_TIMEOUT
default:
configKey = sharedtypes.ConfigOptions_UNKNOWN_CONFIG
}

config := &sharedtypes.ConfigOption{
Key: configKey,
Value: value,
}
endpointConfigs = append(endpointConfigs, config)
}
}

return endpointConfigs
}

func parseEndpointRPCType(endpoint serviceEndpoint) sharedtypes.RPCType {
switch endpoint.RPCType {
case "json_rpc":
return sharedtypes.RPCType_JSON_RPC
default:
return sharedtypes.RPCType_UNKNOWN_RPC
}
}
67 changes: 13 additions & 54 deletions x/supplier/client/cli/tx_stake_supplier.go
Original file line number Diff line number Diff line change
@@ -1,62 +1,48 @@
package cli

import (
"fmt"
"strconv"
"strings"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/cobra"

sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
"github.com/pokt-network/poktroll/x/supplier/types"
)

var _ = strconv.Itoa(0)
var (
flagStakeConfig string
_ = strconv.Itoa(0)
)

func CmdStakeSupplier() *cobra.Command {
// fromAddress & signature is retrieved via `flags.FlagFrom` in the `clientCtx`
cmd := &cobra.Command{
// TODO_HACK: For now we are only specifying the service IDs as a list of of strings separated by commas.
// This needs to be expand to specify the full SupplierServiceConfig. Furthermore, providing a flag to
// a file where SupplierServiceConfig specifying full service configurations in the CLI by providing a flag that accepts a JSON string
Use: "stake-supplier <upokt_amount> <svcId1;url1,svcId2;url2,...,svcIdN;urlN>",
Use: "stake-supplier",
Short: "Stake a supplier",
Long: `Stake an supplier with the provided parameters. This is a broadcast operation that
will stake the tokens and associate them with the supplier specified by the 'from' address.
TODO_HACK: Until proper service configuration files are supported, suppliers must specify the services as a single string
of comma separated values of the form 'service;url' where 'service' is the service ID and 'url' is the service URL.
For example, an application that stakes for 'anvil' could be matched with a supplier staking for 'anvil;http://anvil:8547'.
Example:
$ poktrolld --home=$(POKTROLLD_HOME) tx supplier stake-supplier 1000upokt anvil;http://anvil:8547 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) (err error) {
stakeString := args[0]
servicesArg := args[1]

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
$ poktrolld --home=$(POKTROLLD_HOME) tx supplier stake-supplier --config stake_config.json --keyring-backend test --from $(APP) --node $(POCKET_NODE)`,

stake, err := sdk.ParseCoinNormalized(stakeString)
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) (err error) {
stakeOptions, err := parseStakeConfig(flagStakeConfig)
if err != nil {
return err
}

services, err := hackStringToServices(servicesArg)
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

msg := types.NewMsgStakeSupplier(
clientCtx.GetFromAddress().String(),
stake,
services,
stakeOptions.stake,
stakeOptions.services,
)

if err := msg.ValidateBasic(); err != nil {
Expand All @@ -67,35 +53,8 @@ $ poktrolld --home=$(POKTROLLD_HOME) tx supplier stake-supplier 1000upokt anvil;
},
}

cmd.Flags().StringVar(&flagStakeConfig, "config", "", "Path to the stake config file")
flags.AddTxFlagsToCmd(cmd)

return cmd
}

// TODO_BLOCKER, TODO_HACK: The supplier stake command should take an argument
// or flag that points to a file containing all the services configurations & specifications.
// As a quick workaround, we just need the service & url to get things working for now.
func hackStringToServices(servicesArg string) ([]*sharedtypes.SupplierServiceConfig, error) {
supplierServiceConfig := make([]*sharedtypes.SupplierServiceConfig, 0)
serviceStrings := strings.Split(servicesArg, ",")
for _, serviceString := range serviceStrings {
serviceParts := strings.Split(serviceString, ";")
if len(serviceParts) != 2 {
return nil, fmt.Errorf("invalid service string: %s. Expected it to be of the form 'service;url'", serviceString)
}
service := &sharedtypes.SupplierServiceConfig{
Service: &sharedtypes.Service{
Id: serviceParts[0],
},
Endpoints: []*sharedtypes.SupplierEndpoint{
{
Url: serviceParts[1],
RpcType: sharedtypes.RPCType_JSON_RPC,
Configs: make([]*sharedtypes.ConfigOption, 0),
},
},
}
supplierServiceConfig = append(supplierServiceConfig, service)
}
return supplierServiceConfig, nil
}

0 comments on commit c667c9e

Please sign in to comment.