diff --git a/Makefile b/Makefile index 1d1831bb0..7e53da2a5 100644 --- a/Makefile +++ b/Makefile @@ -159,7 +159,7 @@ rebuild_client_start: docker_check ## Rebuild and run a client daemon which is o .PHONY: client_connect client_connect: docker_check ## Connect to the running client debugging daemon - docker exec -it client /bin/bash -c "POCKET_P2P_IS_CLIENT_ONLY=true go run -tags=debug app/client/*.go debug --remote_cli_url=http://validator1:50832" + docker exec -it client /bin/bash -c "go run -tags=debug app/client/*.go DebugUI" .PHONY: build_and_watch build_and_watch: ## Continous build Pocket's main entrypoint as files change @@ -525,7 +525,7 @@ localnet_up: ## Starts up a k8s LocalNet with all necessary dependencies (tl;dr .PHONY: localnet_client_debug localnet_client_debug: ## Opens a `client debug` cli to interact with blockchain (e.g. change pacemaker mode, reset to genesis, etc). Though the node binary updates automatiacally on every code change (i.e. hot reloads), if client is already open you need to re-run this command to execute freshly compiled binary. - kubectl exec -it deploy/dev-cli-client --container pocket -- p1 debug --remote_cli_url http://pocket-validators:50832 + kubectl exec -it deploy/dev-cli-client --container pocket -- p1 DebugUI .PHONY: localnet_shell localnet_shell: ## Opens a shell in the pod that has the `client` cli available. The binary updates automatically whenever the code changes (i.e. hot reloads). diff --git a/app/client/cli/cmd.go b/app/client/cli/cmd.go index 8e3f975bb..f9632cd0c 100644 --- a/app/client/cli/cmd.go +++ b/app/client/cli/cmd.go @@ -48,7 +48,6 @@ var rootCmd = &cobra.Command{ PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // by this time, the config path should be set cfg = 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") return nil diff --git a/app/client/cli/debug.go b/app/client/cli/debug.go index f23a278d7..5ff6d521b 100644 --- a/app/client/cli/debug.go +++ b/app/client/cli/debug.go @@ -41,16 +41,20 @@ var ( ) func init() { - dbg := NewDebugCommand() - dbg.AddCommand(NewDebugSubCommands()...) + dbgUI := newDebugUICommand() + dbgUI.AddCommand(newDebugUISubCommands()...) + rootCmd.AddCommand(dbgUI) + + dbg := newDebugCommand() + dbg.AddCommand(debugCommands()...) rootCmd.AddCommand(dbg) } -// NewDebugSubCommands builds out the list of debug subcommands by matching the +// newDebugUISubCommands builds out the list of debug subcommands by matching the // handleSelect dispatch to the appropriate command. // * To add a debug subcommand, you must add it to the `items` array and then // write a function handler to match for it in `handleSelect`. -func NewDebugSubCommands() []*cobra.Command { +func newDebugUISubCommands() []*cobra.Command { commands := make([]*cobra.Command, len(items)) for idx, promptItem := range items { commands[idx] = &cobra.Command{ @@ -65,17 +69,67 @@ func NewDebugSubCommands() []*cobra.Command { return commands } -// NewDebugCommand returns the cobra CLI for the Debug command. -func NewDebugCommand() *cobra.Command { +// newDebugUICommand returns the cobra CLI for the Debug UI interface. +func newDebugUICommand() *cobra.Command { return &cobra.Command{ - Use: "debug", - Short: "Debug utility for rapid development", + Aliases: []string{"dui"}, + Use: "DebugUI", + Short: "Debug selection ui for rapid development", Args: cobra.MaximumNArgs(0), PersistentPreRunE: helpers.P2PDependenciesPreRunE, RunE: runDebug, } } +// 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) { for { if selection, err := promptGetInput(); err == nil { diff --git a/app/client/cli/docgen/main.go b/app/client/cli/docgen/main.go index b69c7dfd2..6cdcf70c0 100644 --- a/app/client/cli/docgen/main.go +++ b/app/client/cli/docgen/main.go @@ -9,6 +9,9 @@ import ( "github.com/spf13/cobra/doc" ) +// TODO: Document that `Aliases` should be either dromedaryCase, one word lowercase, or just a few lowercase letters. +// TODO: Document that `Use` should also be PascalCase + func main() { workingDir, err := os.Getwd() if err != nil { diff --git a/app/client/cli/helpers/setup.go b/app/client/cli/helpers/setup.go index 956102a04..34605bd1e 100644 --- a/app/client/cli/helpers/setup.go +++ b/app/client/cli/helpers/setup.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/pokt-network/pocket/app/client/cli/flags" "github.com/pokt-network/pocket/logger" @@ -11,15 +12,23 @@ import ( rpcCHP "github.com/pokt-network/pocket/p2p/providers/current_height_provider/rpc" rpcPSP "github.com/pokt-network/pocket/p2p/providers/peerstore_provider/rpc" "github.com/pokt-network/pocket/runtime" + "github.com/pokt-network/pocket/runtime/configs" "github.com/pokt-network/pocket/shared/modules" ) // 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 is to keep backwards compatibility with localnet + // 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") + // 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") + runtimeMgr := runtime.NewManagerFromFiles( flags.ConfigPath, genesisPath, runtime.WithClientDebugMode(), diff --git a/build/deployments/docker-compose.yaml b/build/deployments/docker-compose.yaml index 4c4caab48..366967711 100755 --- a/build/deployments/docker-compose.yaml +++ b/build/deployments/docker-compose.yaml @@ -166,6 +166,8 @@ services: security_opt: - "seccomp:unconfined" environment: + # BUG: The `SERVICER1_SERVICER_ENABLED` env var is not currnetly visible in the `command` above and needs to be investigate + - SERVICER1_SERVICER_ENABLED=true - POCKET_RPC_USE_CORS=true # Uncomment to enable DLV debugging # - DEBUG_PORT=7085 diff --git a/build/localnet/manifests/cli-client.yaml b/build/localnet/manifests/cli-client.yaml index 719668ac6..c173910aa 100644 --- a/build/localnet/manifests/cli-client.yaml +++ b/build/localnet/manifests/cli-client.yaml @@ -38,8 +38,6 @@ spec: memory: "512Mi" cpu: "4" env: - - name: POCKET_P2P_IS_CLIENT_ONLY - value: "true" - name: CONFIG_PATH value: "/var/pocket/config/config.json" - name: GENESIS_PATH @@ -75,9 +73,10 @@ spec: value: validator1 # Any host that is visible and connected to the cluster can be arbitrarily selected as the RPC host - name: POCKET_REMOTE_CLI_URL - value: http://full-node-001-pocket:50832 - # TECHDEBT(#678): debug client requires hostname to participate - # in P2P networking. + # CONSIDERATION: Should we use a validator or full node for this? + value: http://pocket-validators:50832 + # value: http://full-node-001-pocket:50832 + # TECHDEBT(#678): debug client requires hostname to participate in P2P networking. - name: POCKET_P2P_HOSTNAME value: "127.0.0.1" volumeMounts: diff --git a/e2e/tests/steps_init_test.go b/e2e/tests/steps_init_test.go index f981f5793..ee680cd82 100644 --- a/e2e/tests/steps_init_test.go +++ b/e2e/tests/steps_init_test.go @@ -23,8 +23,8 @@ import ( var e2eLogger = pocketLogger.Global.CreateLoggerForModule("e2e") const ( - // defines the host & port scheme that LocalNet uses for naming validators. - // e.g. validator-001 thru validator-999 + // Each actor is represented e.g. validator-001-pocket:42069 thru validator-999-pocket:42069. + // Defines the host & port scheme that LocalNet uses for naming actors. validatorServiceURLTmpl = "validator-%s-pocket:%d" // validatorA maps to suffix ID 001 and is also used by the cluster-manager // though it has no special permissions. @@ -43,6 +43,7 @@ type rootSuite struct { // clientset is the kubernetes API we acquire from the user's $HOME/.kube/config clientset *kubernetes.Clientset // validator holds command results between runs and reports errors to the test suite + // TECHDEBT: Rename `validator` to something more appropriate validator *validatorPod // validatorA maps to suffix ID 001 of the kube pod that we use as our control agent } @@ -148,9 +149,7 @@ func (s *rootSuite) unstakeValidator() { } // getPrivateKey generates a new keypair from the private hex key that we get from the clientset -func (s *rootSuite) getPrivateKey( - validatorId string, -) cryptoPocket.PrivateKey { +func (s *rootSuite) getPrivateKey(validatorId string) cryptoPocket.PrivateKey { privHexString := s.validatorKeys[validatorId] privateKey, err := cryptoPocket.NewPrivateKey(privHexString) require.NoErrorf(s, err, "failed to extract privkey") @@ -161,6 +160,8 @@ func (s *rootSuite) getPrivateKey( // getClientset uses the default path `$HOME/.kube/config` to build a kubeconfig // and then connects to that cluster and returns a *Clientset or an error func getClientset(t gocuke.TestingT) (*kubernetes.Clientset, error) { + t.Helper() + userHomeDir, err := os.UserHomeDir() if err != nil { return nil, fmt.Errorf("failed to get home dir: %w", err) diff --git a/p2p/background/router.go b/p2p/background/router.go index 5e6254d20..7899f7817 100644 --- a/p2p/background/router.go +++ b/p2p/background/router.go @@ -5,6 +5,7 @@ package background import ( "context" "fmt" + "time" dht "github.com/libp2p/go-libp2p-kad-dht" pubsub "github.com/libp2p/go-libp2p-pubsub" @@ -32,6 +33,13 @@ var ( _ backgroundRouterFactory = &backgroundRouter{} ) +// TECHDEBT: Make these values configurable +// TECHDEBT: Consider using an exponential backoff instead +const ( + connectMaxRetries = 5 + connectRetryTimeout = time.Second * 2 +) + type backgroundRouterFactory = modules.FactoryWithConfig[typesP2P.Router, *config.BackgroundConfig] // backgroundRouter implements `typesP2P.Router` for use with all P2P participants. @@ -357,13 +365,30 @@ func (rtr *backgroundRouter) bootstrap(ctx context.Context) error { return nil } - if err := rtr.host.Connect(ctx, libp2pAddrInfo); err != nil { + if err := rtr.connectWithRetry(ctx, libp2pAddrInfo); err != nil { return fmt.Errorf("connecting to peer: %w", err) } } return nil } +// connectWithRetry attempts to connect to the given peer, retrying up to connectMaxRetries times +// and waiting connectRetryTimeout between each attempt. +func (rtr *backgroundRouter) connectWithRetry(ctx context.Context, libp2pAddrInfo libp2pPeer.AddrInfo) error { + var err error + for i := 0; i < connectMaxRetries; i++ { + err = rtr.host.Connect(ctx, libp2pAddrInfo) + if err == nil { + return nil + } + + fmt.Printf("Failed to connect (attempt %d), retrying in %v...\n", i+1, connectRetryTimeout) + time.Sleep(connectRetryTimeout) + } + + return fmt.Errorf("failed to connect after %d attempts, last error: %w", 5, err) +} + // topicValidator is used in conjunction with libp2p-pubsub's notion of "topic // validaton". It is used for arbitrary and concurrent pre-propagation validation // of messages. diff --git a/persistence/block.go b/persistence/block.go index 57ae39f4d..c443f489f 100644 --- a/persistence/block.go +++ b/persistence/block.go @@ -24,6 +24,7 @@ func (p *persistenceModule) TransactionExists(transactionHash string) (bool, err } return false, err } + return true, nil } diff --git a/runtime/configs/proto/p2p_config.proto b/runtime/configs/proto/p2p_config.proto index e01cea09a..c331176fa 100644 --- a/runtime/configs/proto/p2p_config.proto +++ b/runtime/configs/proto/p2p_config.proto @@ -12,6 +12,6 @@ message P2PConfig { uint32 port = 3; conn.ConnectionType connection_type = 4; uint64 max_nonces = 5; // used to limit the number of nonces that can be stored before a FIFO mechanism is used to remove the oldest nonces and make space for the new ones - bool is_client_only = 6; + bool is_client_only = 6; // TECHDEBT(bryanchriswhite,olshansky): Re-evaluate if this is still needed string bootstrap_nodes_csv = 7; // string in the format "http://somenode:50832,http://someothernode:50832". Refer to `p2p/module_test.go` for additional details. }