Skip to content

Commit

Permalink
[CLI] Consistent config/flag parsing & common helpers (#891)
Browse files Browse the repository at this point in the history
Co-authored-by: harry <[email protected]>
Co-authored-by: Daniel Olshansky <[email protected]>
  • Loading branch information
3 people authored and red-0ne committed Aug 2, 2023
1 parent 3165b8d commit 990321e
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 138 deletions.
155 changes: 25 additions & 130 deletions app/client/cli/debug.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package cli

import (
"errors"
"fmt"
"os"

"github.com/manifoldco/promptui"
Expand All @@ -11,10 +9,7 @@ import (

"github.com/pokt-network/pocket/app/client/cli/helpers"
"github.com/pokt-network/pocket/logger"
"github.com/pokt-network/pocket/p2p/providers/peerstore_provider"
typesP2P "github.com/pokt-network/pocket/p2p/types"
"github.com/pokt-network/pocket/shared/messaging"
"github.com/pokt-network/pocket/shared/modules"
)

// TECHDEBT: Lowercase variables / constants that do not need to be exported.
Expand All @@ -28,26 +23,20 @@ const (
PromptSendBlockRequest string = "BlockRequest (broadcast)"
)

var (
items = []string{
PromptPrintNodeState,
PromptTriggerNextView,
PromptTogglePacemakerMode,
PromptResetToGenesis,
PromptShowLatestBlockInStore,
PromptSendMetadataRequest,
PromptSendBlockRequest,
}
)
var items = []string{
PromptPrintNodeState,
PromptTriggerNextView,
PromptTogglePacemakerMode,
PromptResetToGenesis,
PromptShowLatestBlockInStore,
PromptSendMetadataRequest,
PromptSendBlockRequest,
}

func init() {
dbgUI := newDebugUICommand()
dbgUI.AddCommand(newDebugUISubCommands()...)
rootCmd.AddCommand(dbgUI)

dbg := newDebugCommand()
dbg.AddCommand(debugCommands()...)
rootCmd.AddCommand(dbg)
}

// newDebugUISubCommands builds out the list of debug subcommands by matching the
Expand All @@ -60,7 +49,7 @@ func newDebugUISubCommands() []*cobra.Command {
commands[idx] = &cobra.Command{
Use: promptItem,
PersistentPreRunE: helpers.P2PDependenciesPreRunE,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cobra.Command, _ []string) {
handleSelect(cmd, cmd.Use)
},
ValidArgs: items,
Expand All @@ -81,56 +70,7 @@ func newDebugUICommand() *cobra.Command {
}
}

// newDebugCommand returns the cobra CLI for the Debug command.
func newDebugCommand() *cobra.Command {
return &cobra.Command{
Use: "Debug",
Aliases: []string{"d"},
Short: "Debug utility for rapid development",
Args: cobra.MaximumNArgs(1),
PersistentPreRunE: helpers.P2PDependenciesPreRunE,
}
}

func debugCommands() []*cobra.Command {
cmds := []*cobra.Command{
{
Use: "TriggerView",
Aliases: []string{"next", "trigger", "view"},
Short: "Trigger the next view in consensus",
Long: "Sends a message to all visible nodes on the network to start the next view (height/step/round) in consensus",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
m := &messaging.DebugMessage{
Action: messaging.DebugMessageAction_DEBUG_CONSENSUS_TRIGGER_NEXT_VIEW,
Type: messaging.DebugMessageRoutingType_DEBUG_MESSAGE_TYPE_BROADCAST,
Message: nil,
}
broadcastDebugMessage(cmd, m)
return nil
},
},
{
Use: "TogglePacemakerMode",
Short: "Toggle the pacemaker",
Long: "Toggle the consensus pacemaker either on or off so the chain progresses on its own or loses liveness",
Aliases: []string{"togglePaceMaker"},
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
m := &messaging.DebugMessage{
Action: messaging.DebugMessageAction_DEBUG_CONSENSUS_TOGGLE_PACE_MAKER_MODE,
Type: messaging.DebugMessageRoutingType_DEBUG_MESSAGE_TYPE_BROADCAST,
Message: nil,
}
broadcastDebugMessage(cmd, m)
return nil
},
},
}
return cmds
}

func runDebug(cmd *cobra.Command, args []string) (err error) {
func runDebug(cmd *cobra.Command, _ []string) (err error) {
for {
if selection, err := promptGetInput(); err == nil {
handleSelect(cmd, selection)
Expand Down Expand Up @@ -218,32 +158,20 @@ func handleSelect(cmd *cobra.Command, selection string) {
}
}

// Broadcast to the entire validator set
// Broadcast to the entire network.
func broadcastDebugMessage(cmd *cobra.Command, debugMsg *messaging.DebugMessage) {
anyProto, err := anypb.New(debugMsg)
if err != nil {
logger.Global.Fatal().Err(err).Msg("Failed to create Any proto")
}

// TODO(olshansky): Once we implement the cleanup layer in RainTree, we'll be able to use
// broadcast. The reason it cannot be done right now is because this client is not in the
// address book of the actual validator nodes, so `validator1` never receives the message.
// p2pMod.Broadcast(anyProto)

pstore, err := fetchPeerstore(cmd)
bus, err := helpers.GetBusFromCmd(cmd)
if err != nil {
logger.Global.Fatal().Err(err).Msg("Unable to retrieve the pstore")
logger.Global.Fatal().Err(err).Msg("Failed to retrieve bus from command")
}
for _, val := range pstore.GetPeerList() {
addr := val.GetAddress()
if err != nil {
logger.Global.Fatal().Err(err).Msg("Failed to convert validator address into pocketCrypto.Address")
}
if err := helpers.P2PMod.Send(addr, anyProto); err != nil {
logger.Global.Error().Err(err).Msg("Failed to send debug message")
}
if err := bus.GetP2PModule().Broadcast(anyProto); err != nil {
logger.Global.Error().Err(err).Msg("Failed to broadcast debug message")
}

}

// Send to just a single (i.e. first) validator in the set
Expand All @@ -253,62 +181,29 @@ func sendDebugMessage(cmd *cobra.Command, debugMsg *messaging.DebugMessage) {
logger.Global.Error().Err(err).Msg("Failed to create Any proto")
}

pstore, err := fetchPeerstore(cmd)
pstore, err := helpers.FetchPeerstore(cmd)
if err != nil {
logger.Global.Fatal().Err(err).Msg("Unable to retrieve the pstore")
}

var validatorAddress []byte
if pstore.Size() == 0 {
logger.Global.Fatal().Msg("No validators found")
}

// if the message needs to be broadcast, it'll be handled by the business logic of the message handler
validatorAddress = pstore.GetPeerList()[0].GetAddress()
//
// TODO(#936): The statement above is false. Using `#Send()` will only
// be unicast with no opportunity for further propagation.
firstStakedActorAddress := pstore.GetPeerList()[0].GetAddress()
if err != nil {
logger.Global.Fatal().Err(err).Msg("Failed to convert validator address into pocketCrypto.Address")
}

if err := helpers.P2PMod.Send(validatorAddress, anyProto); err != nil {
logger.Global.Error().Err(err).Msg("Failed to send debug message")
}
}

// fetchPeerstore retrieves the providers from the CLI context and uses them to retrieve the address book for the current height
func fetchPeerstore(cmd *cobra.Command) (typesP2P.Peerstore, error) {
bus, ok := helpers.GetValueFromCLIContext[modules.Bus](cmd, helpers.BusCLICtxKey)
if !ok || bus == nil {
return nil, errors.New("retrieving bus from CLI context")
}
// TECHDEBT(#810, #811): use `bus.GetPeerstoreProvider()` after peerstore provider
// is retrievable as a proper submodule
pstoreProvider, err := bus.GetModulesRegistry().GetModule(peerstore_provider.PeerstoreProviderSubmoduleName)
if err != nil {
return nil, errors.New("retrieving peerstore provider")
}
currentHeightProvider := bus.GetCurrentHeightProvider()

height := currentHeightProvider.CurrentHeight()
pstore, err := pstoreProvider.(peerstore_provider.PeerstoreProvider).GetStakedPeerstoreAtHeight(height)
if err != nil {
return nil, fmt.Errorf("retrieving peerstore at height %d", height)
}
// Inform the client's main P2P that a the blockchain is at a new height so it can, if needed, update its view of the validator set
err = sendConsensusNewHeightEventToP2PModule(height, bus)
bus, err := helpers.GetBusFromCmd(cmd)
if err != nil {
return nil, errors.New("sending consensus new height event")
logger.Global.Fatal().Err(err).Msg("Failed to retrieve bus from command")
}
return pstore, nil
}

// sendConsensusNewHeightEventToP2PModule mimicks the consensus module sending a ConsensusNewHeightEvent to the p2p module
// This is necessary because the debug client is not a validator and has no consensus module but it has to update the peerstore
// depending on the changes in the validator set.
// TODO(#613): Make the debug client mimic a full node.
func sendConsensusNewHeightEventToP2PModule(height uint64, bus modules.Bus) error {
newHeightEvent, err := messaging.PackMessage(&messaging.ConsensusNewHeightEvent{Height: height})
if err != nil {
logger.Global.Fatal().Err(err).Msg("Failed to pack consensus new height event")
if err := bus.GetP2PModule().Send(firstStakedActorAddress, anyProto); err != nil {
logger.Global.Error().Err(err).Msg("Failed to send debug message")
}
return bus.GetP2PModule().HandleEvent(newHeightEvent.Content)
}
54 changes: 48 additions & 6 deletions app/client/cli/helpers/common.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,56 @@
package helpers

import (
"errors"
"fmt"

"github.com/spf13/cobra"

"github.com/pokt-network/pocket/logger"
"github.com/pokt-network/pocket/p2p/providers/peerstore_provider"
"github.com/pokt-network/pocket/p2p/types"
"github.com/pokt-network/pocket/runtime"
"github.com/pokt-network/pocket/shared/messaging"
"github.com/pokt-network/pocket/shared/modules"
)

var (
// TECHDEBT: Accept reading this from `Datadir` and/or as a flag.
genesisPath = runtime.GetEnv("GENESIS_PATH", "build/config/genesis.json")
// TECHDEBT: Accept reading this from `Datadir` and/or as a flag.
var genesisPath = runtime.GetEnv("GENESIS_PATH", "build/config/genesis.json")

// P2PMod is initialized in order to broadcast a message to the local network
P2PMod modules.P2PModule
)
// FetchPeerstore retrieves the providers from the CLI context and uses them to retrieve the address book for the current height
func FetchPeerstore(cmd *cobra.Command) (types.Peerstore, error) {
bus, err := GetBusFromCmd(cmd)
if err != nil {
return nil, err
}
// TECHDEBT(#811): use `bus.GetPeerstoreProvider()` after peerstore provider
// is retrievable as a proper submodule
pstoreProvider, err := bus.GetModulesRegistry().GetModule(peerstore_provider.PeerstoreProviderSubmoduleName)
if err != nil {
return nil, errors.New("retrieving peerstore provider")
}
currentHeightProvider := bus.GetCurrentHeightProvider()
height := currentHeightProvider.CurrentHeight()
pstore, err := pstoreProvider.(peerstore_provider.PeerstoreProvider).GetStakedPeerstoreAtHeight(height)
if err != nil {
return nil, fmt.Errorf("retrieving peerstore at height %d", height)
}
// Inform the client's main P2P that a the blockchain is at a new height so it can, if needed, update its view of the validator set
if err := sendConsensusNewHeightEventToP2PModule(height, bus); err != nil {
return nil, errors.New("sending consensus new height event")
}
return pstore, nil
}

// sendConsensusNewHeightEventToP2PModule mimicks the consensus module sending a ConsensusNewHeightEvent to the p2p module
// This is necessary because the debug client is not a validator and has no consensus module but it has to update the peerstore
// depending on the changes in the validator set.
// TODO(#613): Make the debug client mimic a full node.
// TECHDEBT: This may no longer be required (https://github.com/pokt-network/pocket/pull/891/files#r1262710098)
func sendConsensusNewHeightEventToP2PModule(height uint64, bus modules.Bus) error {
newHeightEvent, err := messaging.PackMessage(&messaging.ConsensusNewHeightEvent{Height: height})
if err != nil {
logger.Global.Fatal().Err(err).Msg("Failed to pack consensus new height event")
}
return bus.GetP2PModule().HandleEvent(newHeightEvent.Content)
}
14 changes: 14 additions & 0 deletions app/client/cli/helpers/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ package helpers

import (
"context"
"fmt"

"github.com/spf13/cobra"

"github.com/pokt-network/pocket/shared/modules"
)

const BusCLICtxKey cliContextKey = "bus"

var ErrCxtFromBus = fmt.Errorf("could not get context from bus")

// NOTE: this is required by the linter, otherwise a simple string constant would have been enough
type cliContextKey string

Expand All @@ -19,3 +24,12 @@ func GetValueFromCLIContext[T any](cmd *cobra.Command, key cliContextKey) (T, bo
value, ok := cmd.Context().Value(key).(T)
return value, ok
}

func GetBusFromCmd(cmd *cobra.Command) (modules.Bus, error) {
bus, ok := GetValueFromCLIContext[modules.Bus](cmd, BusCLICtxKey)
if !ok {
return nil, ErrCxtFromBus
}

return bus, nil
}
20 changes: 18 additions & 2 deletions app/client/cli/helpers/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,27 @@ import (
"github.com/pokt-network/pocket/shared/modules"
)

// debugPrivKey is used in the generation of a runtime config to provide a private key to the P2P and Consensus modules
// this is not a private key used for sending transactions, but is used for the purposes of broadcasting messages etc.
// this must be done as the CLI does not take a node configuration file and still requires a Private Key for modules
const debugPrivKey = "09fc8ee114e678e665d09179acb9a30060f680df44ba06b51434ee47940a8613be19b2b886e743eb1ff7880968d6ce1a46350315e569243e747a227ee8faec3d"

// P2PDependenciesPreRunE initializes peerstore & current height providers, and a
// p2p module which consumes them. Everything is registered to the bus.
func P2PDependenciesPreRunE(cmd *cobra.Command, _ []string) error {
// TECHDEBT: this was being used for backwards compatibility with LocalNet and need to re-evaluate if its still necessary
flags.ConfigPath = runtime.GetEnv("CONFIG_PATH", "build/config/config.validator1.json")
configs.ParseConfig(flags.ConfigPath)

// set final `remote_cli_url` value; order of precedence: flag > env var > config > default
flags.RemoteCLIURL = viper.GetString("remote_cli_url")

// By this time, the config path should be set.
// This is only being called for viper related side effects
// TECHDEBT(#907): refactor and improve how viper is used to parse configs throughout the codebase
_ = configs.ParseConfig(flags.ConfigPath)
// set final `remote_cli_url` value; order of precedence: flag > env var > config > default
flags.RemoteCLIURL = viper.GetString("remote_cli_url")

// By this time, the config path should be set.
// This is only being called for viper related side effects
Expand All @@ -32,7 +48,7 @@ func P2PDependenciesPreRunE(cmd *cobra.Command, _ []string) error {
runtimeMgr := runtime.NewManagerFromFiles(
flags.ConfigPath, genesisPath,
runtime.WithClientDebugMode(),
runtime.WithRandomPK(),
runtime.WithPK(debugPrivKey),
)

bus := runtimeMgr.GetBus()
Expand Down Expand Up @@ -79,7 +95,7 @@ func setupAndStartP2PModule(rm runtime.Manager) {
}

var ok bool
P2PMod, ok = mod.(modules.P2PModule)
P2PMod, ok := mod.(modules.P2PModule)
if !ok {
logger.Global.Fatal().Msgf("unexpected P2P module type: %T", mod)
}
Expand Down
1 change: 1 addition & 0 deletions runtime/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func WithRandomPK() func(*Manager) {
return WithPK(privateKey.String())
}

// TECHDEBT(#750): separate consensus and P2P (identity vs communication) keys.
func WithPK(pk string) func(*Manager) {
return func(b *Manager) {
if b.config.Consensus == nil {
Expand Down

0 comments on commit 990321e

Please sign in to comment.