Skip to content

Commit

Permalink
reconcile: stake supplier CLI commands
Browse files Browse the repository at this point in the history
  • Loading branch information
bryanchriswhite committed Feb 14, 2024
1 parent 8ca9b82 commit 2168765
Show file tree
Hide file tree
Showing 2 changed files with 364 additions and 0 deletions.
67 changes: 67 additions & 0 deletions x/supplier/module/tx_stake_supplier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package supplier

import (
"os"
"strconv"

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

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

var (
flagStakeConfig string
_ = strconv.Itoa(0) // Part of the default ignite imports
)

func CmdStakeSupplier() *cobra.Command {
// fromAddress & signature is retrieved via `flags.FlagFrom` in the `clientCtx`
cmd := &cobra.Command{
Use: "stake-supplier --config <config_file.yaml>",
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.
Example:
$ poktrolld --home=$(POKTROLLD_HOME) tx supplier stake-supplier --config stake_config.yaml --keyring-backend test --from $(APP) --node $(POCKET_NODE)`,

Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, _ []string) (err error) {
configContent, err := os.ReadFile(flagStakeConfig)
if err != nil {
return err
}

supplierStakeConfigs, err := config.ParseSupplierConfigs(configContent)
if err != nil {
return err
}

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

msg := types.NewMsgStakeSupplier(
clientCtx.GetFromAddress().String(),
supplierStakeConfigs.StakeAmount,
supplierStakeConfigs.Services,
)

if err := msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

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

return cmd
}
297 changes: 297 additions & 0 deletions x/supplier/module/tx_stake_supplier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
package supplier_test

import (
"fmt"
"testing"

sdkerrors "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/testutil"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/status"

"github.com/pokt-network/poktroll/testutil/network"
"github.com/pokt-network/poktroll/testutil/yaml"
supplier "github.com/pokt-network/poktroll/x/supplier/module"
"github.com/pokt-network/poktroll/x/supplier/types"
)

func TestCLI_StakeSupplier(t *testing.T) {
net, _ := networkWithSupplierObjects(t, 2)
val := net.Validators[0]
ctx := val.ClientCtx

// Create a keyring and add an account for the supplier to be staked
kr := ctx.Keyring
accounts := testutil.CreateKeyringAccounts(t, kr, 1)
supplierAccount := accounts[0]

// Update the context with the new keyring
ctx = ctx.WithKeyring(kr)

// Common args used for all requests
commonArgs := []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(net.Config.BondDenom, sdkmath.NewInt(10))).String()),
}

defaultConfig := `
stake_amount: 1000upokt
services:
- service_id: svc1
endpoints:
- url: http://pokt.network:8081
rpc_type: json_rpc
`

tests := []struct {
desc string
address string
config string
err *sdkerrors.Error
}{
// Happy Paths
{
desc: "stake supplier: valid",
address: supplierAccount.Address.String(),
config: defaultConfig,
},

// Error Paths - Address Related
{
desc: "stake supplier: missing address",
// address: "explicitly missing",
err: types.ErrSupplierInvalidAddress,
config: defaultConfig,
},
{
desc: "stake supplier: invalid address",
address: "invalid",
err: types.ErrSupplierInvalidAddress,
config: defaultConfig,
},

// Error Paths - Stake Related
{
desc: "stake supplier: missing stake",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidStake,
config: `
# explicitly omitted stake
services:
- service_id: svc1
endpoints:
- url: http://pokt.network:8081
rpc_type: json_rpc
`,
},
{
desc: "stake supplier: invalid stake denom",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidStake,
config: `
stake_amount: 1000invalid
services:
- service_id: svc1
endpoints:
- url: http://pokt.network:8081
rpc_type: json_rpc
`,
},
{
desc: "stake supplier: invalid stake amount (zero)",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidStake,
config: `
stake_amount: 0upokt
services:
- service_id: svc1
endpoints:
- url: http://pokt.network:8081
rpc_type: json_rpc
`,
},
{
desc: "stake supplier: invalid stake amount (negative)",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidStake,
config: `
stake_amount: -1000upokt
services:
- service_id: svc1
endpoints:
- url: http://pokt.network:8081
rpc_type: json_rpc
`,
},

// Happy Paths - Service Related
{
desc: "services_test: valid multiple services",
address: supplierAccount.Address.String(),
config: `
stake_amount: 1000upokt
services:
- service_id: svc1
endpoints:
- url: http://pokt.network:8081
rpc_type: json_rpc
- service_id: svc2
endpoints:
- url: http://pokt.network:8082
rpc_type: json_rpc
`,
},
{
desc: "services_test: valid localhost",
address: supplierAccount.Address.String(),
config: `
stake_amount: 1000upokt
services:
- service_id: svc1
endpoints:
- url: http://127.0.0.1:8082
rpc_type: json_rpc
`,
},
{
desc: "services_test: valid loopback",
address: supplierAccount.Address.String(),
config: `
stake_amount: 1000upokt
services:
- service_id: svc1
endpoints:
- url: http://localhost:8082
rpc_type: json_rpc
`,
},
{
desc: "services_test: valid without a pork",
address: supplierAccount.Address.String(),
config: `
stake_amount: 1000upokt
services:
- service_id: svc1
endpoints:
- url: http://pokt.network
rpc_type: json_rpc
`,
},

// Error Paths - Service Related
{
desc: "services_test: invalid services (missing argument)",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidServiceConfig,
// servicesString: "explicitly omitted",
config: `
stake_amount: 1000upokt
`,
},
{
desc: "services_test: invalid services (empty string)",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidServiceConfig,
config: `
stake_amount: 1000upokt
services:
`,
},
{
desc: "services_test: invalid URL",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidServiceConfig,
config: `
stake_amount: 1000upokt
services:
- service_id: svc1
endpoints:
- url: bad_url
rpc_type: json_rpc
`,
},
{
desc: "services_test: missing URLs",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidServiceConfig,
config: `
stake_amount: 1000upokt
services:
- service_id: svc1
- service_id: svc2
`,
},
{
desc: "services_test: missing service IDs",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidServiceConfig,
config: `
stake_amount: 1000upokt
services:
- endpoints:
- url: localhost:8081
rpc_type: json_rpc
- endpoints:
- url: localhost:8082
rpc_type: json_rpc
`,
},
{
desc: "services_test: missing rpc type",
address: supplierAccount.Address.String(),
err: types.ErrSupplierInvalidServiceConfig,
config: `
stake_amount: 1000upokt
services:
- service_id: svc1
endpoints:
- url: localhost:8082
`,
},
}

// Initialize the Supplier Account by sending it some funds from the validator account that is part of genesis
network.InitAccount(t, net, supplierAccount.Address)

// Run the tests
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
// Wait for a new block to be committed
require.NoError(t, net.WaitForNextBlock())

// write the stake config to a file
configPath := testutil.WriteToNewTempFile(t, yaml.NormalizeYAMLIndentation(tt.config)).Name()

// Prepare the arguments for the CLI command
args := []string{
fmt.Sprintf("--config=%s", configPath),
fmt.Sprintf("--%s=%s", flags.FlagFrom, tt.address),
}
args = append(args, commonArgs...)

// Execute the command
outStake, err := clitestutil.ExecTestCLICmd(ctx, supplier.CmdStakeSupplier(), args)

// Validate the error if one is expected
if tt.err != nil {
stat, ok := status.FromError(tt.err)
require.True(t, ok)
require.Contains(t, stat.Message(), tt.err.Error())
return
}
require.NoError(t, err)

// Check the response
var resp sdk.TxResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(outStake.Bytes(), &resp))
require.NotNil(t, resp)
require.NotNil(t, resp.TxHash)
require.Equal(t, uint32(0), resp.Code)
})
}
}

0 comments on commit 2168765

Please sign in to comment.