Skip to content

Commit

Permalink
E2E State Sync Test
Browse files Browse the repository at this point in the history
  • Loading branch information
Olshansk committed Jul 28, 2023
1 parent 6736454 commit e74cc90
Show file tree
Hide file tree
Showing 28 changed files with 663 additions and 292 deletions.
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ temp_test.go
test_results.json
coverage.out

# Output of `make build_and_watch`
main

# generated RPC server and client from openapi.yaml
rpc/server.gen.go
rpc/client.gen.go
Expand Down Expand Up @@ -90,3 +87,6 @@ tools/wiki

# ggshield
.cache_ggshield

# mock temporary files
**/gomock_reflect_*/
3 changes: 0 additions & 3 deletions .tiltignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ temp_test.go
test_results.json
coverage.out

# Output of `make build_and_watch`
main

# generated RPC server and client from openapi.yaml
rpc/server.gen.go
rpc/client.gen.go
Expand Down
34 changes: 14 additions & 20 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -163,33 +163,27 @@ develop_test: docker_check ## Run all of the make commands necessary to develop
make develop_start && \
make test_all

.PHONY: client_start
client_start: docker_check ## Run a client daemon which is only used for debugging purposes
.PHONY: lightweight_localnet_client
lightweight_localnet_client: docker_check ## Run a client daemon which is only used for debugging purposes
# Add `--build` to rebuild the client
${docker-compose} up -d client

.PHONY: rebuild_client_start
rebuild_client_start: docker_check ## Rebuild and run a client daemon which is only used for debugging purposes
${docker-compose} up -d --build client

.PHONY: client_connect
client_connect: docker_check ## Connect to the running client debugging daemon
.PHONY: lightweight_localnet_client_debug
lightweight_localnet_client_debug: docker_check ## Connect to the running client debugging daemon
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
/bin/sh ${PWD}/build/scripts/watch_build.sh
# IMPROVE: Avoid building the binary on every shell execution and sync it from local instead
.PHONY: lightweight_localnet_shell
lightweight_localnet_shell: docker_check ## Connect to the running client debugging daemon
docker exec -it client /bin/bash -c "go build -tags=debug -o p1 ./app/client/*.go && chmod +x p1 && mv p1 /usr/bin && echo \"Finished building a new p1 binary\" && /bin/bash"

# TODO(olshansky): Need to think of a Pocket related name for `compose_and_watch`, maybe just `pocket_watch`?
.PHONY: compose_and_watch
compose_and_watch: docker_check db_start monitoring_start ## Run a localnet composed of 4 consensus validators w/ hot reload & debugging
.PHONY: lightweight_localnet
lightweight_localnet: docker_check db_start monitoring_start ## Run a lightweight localnet composed of 4 validators w/ hot reload & debugging
# Add `--build` to rebuild the client
${docker-compose} up --force-recreate validator1 validator2 validator3 validator4 servicer1 fisherman1

.PHONY: rebuild_and_compose_and_watch
rebuild_and_compose_and_watch: docker_check db_start monitoring_start ## Rebuilds the container from scratch and launches compose_and_watch
${docker-compose} up --build --force-recreate validator1 validator2 validator3 validator4 servicer1 fisherman1

.PHONY: db_start
db_start: docker_check ## Start a detached local postgres and admin instance; compose_and_watch is responsible for instantiating the actual schemas
db_start: docker_check ## Start a detached local postgres and admin instance; lightweight_localnet is responsible for instantiating the actual schemas
${docker-compose} up --no-recreate -d db pgadmin

.PHONY: db_cli
Expand Down Expand Up @@ -245,7 +239,7 @@ docker_wipe_nodes: docker_check prompt_user db_drop ## [WARNING] Remove all the
docker ps -a -q --filter="name=node*" | xargs -r -I {} docker rm {}

.PHONY: monitoring_start
monitoring_start: docker_check ## Start grafana, metrics and logging system (this is auto-triggered by compose_and_watch)
monitoring_start: docker_check ## Start grafana, metrics and logging system (this is auto-triggered by lightweight_localnet)
${docker-compose} up --no-recreate -d grafana loki vm

.PHONY: docker_loki_install
Expand Down
137 changes: 116 additions & 21 deletions app/client/cli/debug.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package cli

import (
"fmt"
"log"
"os"
"os/exec"
"time"

"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
"google.golang.org/protobuf/types/known/anypb"

"github.com/pokt-network/pocket/app/client/cli/helpers"
Expand Down Expand Up @@ -34,43 +39,123 @@ var items = []string{
}

func init() {
dbg := newDebugCommand()
dbg.AddCommand(newDebugSubCommands()...)
rootCmd.AddCommand(dbg)

dbgUI := newDebugUICommand()
dbgUI.AddCommand(newDebugUISubCommands()...)
rootCmd.AddCommand(dbgUI)
}

// 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 newDebugUISubCommands() []*cobra.Command {
commands := make([]*cobra.Command, len(items))
for idx, promptItem := range items {
commands[idx] = &cobra.Command{
Use: promptItem,
// 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",
Long: "Debug utility to send fire-and-forget messages to the network for development purposes",
Args: cobra.MaximumNArgs(1),
}
}

// newDebugSubCommands is a list of commands that can be "fired & forgotten" (no selection necessary)
func newDebugSubCommands() []*cobra.Command {
cmds := []*cobra.Command{
{
Use: "PrintNodeState",
Aliases: []string{"print", "state"},
Short: "Prints the node state",
Long: "Sends a message to all visible nodes to log the current state of their consensus",
Args: cobra.ExactArgs(0),
PersistentPreRunE: helpers.P2PDependenciesPreRunE,
Run: func(cmd *cobra.Command, _ []string) {
handleSelect(cmd, cmd.Use)
Run: func(cmd *cobra.Command, args []string) {
runWithSleep(func() {
handleSelect(cmd, PromptPrintNodeState)
})
},
ValidArgs: items,
}
},
{
Use: "ResetToGenesis",
Aliases: []string{"reset", "genesis"},
Short: "Reset to genesis",
Long: "Broadcast a message to all visible nodes to reset the state to genesis",
Args: cobra.ExactArgs(0),
PersistentPreRunE: helpers.P2PDependenciesPreRunE,
Run: func(cmd *cobra.Command, args []string) {
runWithSleep(func() {
handleSelect(cmd, PromptResetToGenesis)
})
},
},
{
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),
PersistentPreRunE: helpers.P2PDependenciesPreRunE,
Run: func(cmd *cobra.Command, args []string) {
runWithSleep(func() {
handleSelect(cmd, PromptTriggerNextView)
})
},
},
{
Use: "TogglePacemakerMode",
Aliases: []string{"toggle", "pcm"},
Short: "Toggle the pacemaker",
Long: "Toggle the consensus pacemaker either on or off so the chain progresses on its own or loses liveness",
Args: cobra.ExactArgs(0),
PersistentPreRunE: helpers.P2PDependenciesPreRunE,
Run: func(cmd *cobra.Command, args []string) {
runWithSleep(func() {
handleSelect(cmd, PromptTogglePacemakerMode)
})
},
},
{
Use: "ScaleActor",
Aliases: []string{"scale"},
Short: "Scales the number of actors up or down",
Long: "Scales the type of actor specified to the number provided",
Args: cobra.ExactArgs(2),
PersistentPreRunE: helpers.P2PDependenciesPreRunE,
Run: func(cmd *cobra.Command, args []string) {
actor := args[0]
numActors := args[1]
validActors := []string{"fishermen", "full_nodes", "servicers", "validators"}
if !slices.Contains(validActors, actor) {
logger.Global.Fatal().Msg("Invalid actor type provided")
}
sedCmd := exec.Command("sed", "-i", fmt.Sprintf("/%s:/,/count:/ s/count: [0-9]*/count: %s/", actor, numActors), "/usr/local/localnet_config.yaml")
err := sedCmd.Run()
if err != nil {
log.Fatal(err)
}
},
},
}
return commands
return cmds
}

// newDebugUICommand returns the cobra CLI for the Debug UI interface.
func newDebugUICommand() *cobra.Command {
return &cobra.Command{
Aliases: []string{"dui"},
Use: "DebugUI",
Short: "Debug selection ui for rapid development",
Aliases: []string{"dui"},
Short: "Debug utility with an interactive UI for development purposes",
Long: "Opens a shell-driven selection UI to view and select from a list of debug actions for development purposes",
Args: cobra.MaximumNArgs(0),
PersistentPreRunE: helpers.P2PDependenciesPreRunE,
RunE: runDebug,
RunE: selectDebugCommand,
}
}

func runDebug(cmd *cobra.Command, _ []string) (err error) {
// selectDebugCommand 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 selectDebugCommand(cmd *cobra.Command, _ []string) error {
for {
if selection, err := promptGetInput(); err == nil {
handleSelect(cmd, selection)
Expand Down Expand Up @@ -158,7 +243,17 @@ func handleSelect(cmd *cobra.Command, selection string) {
}
}

// Broadcast to the entire network.
// HACK: Because of how the p2p module works, we need to surround it with sleep both BEFORE and AFTER the task.
// - Starting the task too early after the debug client initializes results in a lack of visibility of the nodes in the network
// - Ending the task too early before the debug client completes its task results in a lack of propagation of the message or retrieval of the result
// TECHDEBT: There is likely an event based solution to this but it would require a lot more refactoring of the p2p module.
func runWithSleep(task func()) {
time.Sleep(1000 * time.Millisecond)
task()
time.Sleep(1000 * time.Millisecond)
}

// broadcastDebugMessage broadcasts the debug message to the entire visible network.
func broadcastDebugMessage(cmd *cobra.Command, debugMsg *messaging.DebugMessage) {
anyProto, err := anypb.New(debugMsg)
if err != nil {
Expand All @@ -174,7 +269,7 @@ func broadcastDebugMessage(cmd *cobra.Command, debugMsg *messaging.DebugMessage)
}
}

// Send to just a single (i.e. first) validator in the set
// sendDebugMessage sends the debug message to just a single (i.e. first) node visible
func sendDebugMessage(cmd *cobra.Command, debugMsg *messaging.DebugMessage) {
anyProto, err := anypb.New(debugMsg)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion build/config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ It is not recommended at this time to build infrastructure components that rely

## Origin Document

Currently, the Genesis and Configuration generator is necessary to create development `localnet` environments for iterating on V1. A current example (as of 09/2022) of this is the `make compose_and_watch` debug utility that generates a `localnet` using `docker-compose` by injecting the appropriate `config.json` and `genesis.json` files.
Currently, the Genesis and Configuration generator is necessary to create development `localnet` environments for iterating on V1. A current example (as of 09/2022) of this is the `make lightweight_localnet` debug utility that generates a `localnet` using `docker-compose` by injecting the appropriate `config.json` and `genesis.json` files.

## Usage

Expand Down
2 changes: 1 addition & 1 deletion build/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.0.0.1] - 2022-12-29

- Updated all `config*.json` files with the missing `max_mempool_count` value
- Added `is_client_only` to `config1.json` so Viper knows it can be overridden. The config override is done in the Makefile's `client_connect` target. Setting this can be avoided if we merge the changes in https://github.com/pokt-network/pocket/compare/main...issue/cli-viper-environment-vars-fix
- Added `is_client_only` to `config1.json` so Viper knows it can be overridden. The config override is done in the Makefile's `lightweight_localnet_client_debug` target. Setting this can be avoided if we merge the changes in https://github.com/pokt-network/pocket/compare/main...issue/cli-viper-environment-vars-fix

## [0.0.0.0] - 2022-12-22

Expand Down
Loading

0 comments on commit e74cc90

Please sign in to comment.