diff --git a/beacon-chain/core/state/BUILD.bazel b/beacon-chain/core/state/BUILD.bazel index 4deb3c76a59d..22d18db1ad56 100644 --- a/beacon-chain/core/state/BUILD.bazel +++ b/beacon-chain/core/state/BUILD.bazel @@ -12,11 +12,12 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/beacon-chain/core/state", visibility = [ "//beacon-chain:__subpackages__", + "//endtoend:__pkg__", "//shared/interop:__pkg__", "//shared/testutil:__pkg__", "//tools/benchmark-files-gen:__pkg__", "//tools/genesis-state-gen:__pkg__", - "//endtoend:__pkg__", + "//tools/pcli:__pkg__", ], deps = [ "//beacon-chain/cache:go_default_library", diff --git a/beacon-chain/state/BUILD.bazel b/beacon-chain/state/BUILD.bazel index 5a50640d2288..5fa378a0599d 100644 --- a/beacon-chain/state/BUILD.bazel +++ b/beacon-chain/state/BUILD.bazel @@ -16,6 +16,7 @@ go_library( "//shared/benchutil:__pkg__", "//shared/testutil:__pkg__", "//tools/benchmark-files-gen:__pkg__", + "//tools/pcli:__pkg__", ], deps = [ "//beacon-chain/core/state/stateutils:go_default_library", diff --git a/beacon-chain/state/stateutil/BUILD.bazel b/beacon-chain/state/stateutil/BUILD.bazel index 544fa1e5728a..6d88b7795426 100644 --- a/beacon-chain/state/stateutil/BUILD.bazel +++ b/beacon-chain/state/stateutil/BUILD.bazel @@ -18,6 +18,7 @@ go_library( "//beacon-chain:__subpackages__", "//proto/testing:__subpackages__", "//shared/testutil:__subpackages__", + "//tools/pcli:__pkg__", ], deps = [ "//proto/beacon/p2p/v1:go_default_library", diff --git a/tools/pcli/BUILD.bazel b/tools/pcli/BUILD.bazel new file mode 100644 index 000000000000..702103f31642 --- /dev/null +++ b/tools/pcli/BUILD.bazel @@ -0,0 +1,70 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") +load("@io_bazel_rules_docker//go:image.bzl", "go_image") +load("@io_bazel_rules_docker//container:container.bzl", "container_bundle") +load("@io_bazel_rules_docker//contrib:push-all.bzl", "docker_push") + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importpath = "github.com/prysmaticlabs/prysm/tools/pcli", + visibility = ["//visibility:private"], + deps = [ + "//beacon-chain/core/state:go_default_library", + "//beacon-chain/state:go_default_library", + "//beacon-chain/state/stateutil:go_default_library", + "//proto/beacon/p2p/v1:go_default_library", + "//shared/version:go_default_library", + "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", + "@com_github_prysmaticlabs_go_ssz//:go_default_library", + "@com_github_sirupsen_logrus//:go_default_library", + "@com_github_x_cray_logrus_prefixed_formatter//:go_default_library", + "@in_gopkg_d4l3k_messagediff_v1//:go_default_library", + "@in_gopkg_urfave_cli_v2//:go_default_library", + ], +) + +go_image( + name = "image", + srcs = ["main.go"], + base = "//tools:cc_image", + goarch = "amd64", + goos = "linux", + importpath = "github.com/prysmaticlabs/prysm/tools/pcli", + race = "off", + tags = ["manual"], + visibility = ["//visibility:private"], + deps = [ + "//beacon-chain/core/state:go_default_library", + "//beacon-chain/state:go_default_library", + "//beacon-chain/state/stateutil:go_default_library", + "//proto/beacon/p2p/v1:go_default_library", + "//shared/version:go_default_library", + "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", + "@com_github_prysmaticlabs_go_ssz//:go_default_library", + "@com_github_sirupsen_logrus//:go_default_library", + "@com_github_x_cray_logrus_prefixed_formatter//:go_default_library", + "@in_gopkg_d4l3k_messagediff_v1//:go_default_library", + "@in_gopkg_urfave_cli_v2//:go_default_library", + ], +) + +go_binary( + name = "pcli", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) + +container_bundle( + name = "image_bundle", + images = { + "gcr.io/prysmaticlabs/prysm/pcli:latest": ":image", + "gcr.io/prysmaticlabs/prysm/pcli:{DOCKER_TAG}": ":image", + }, + tags = ["manual"], +) + +docker_push( + name = "push_images", + bundle = ":image_bundle", + tags = ["manual"], +) diff --git a/tools/pcli/README.md b/tools/pcli/README.md new file mode 100644 index 000000000000..6a4a461ce89e --- /dev/null +++ b/tools/pcli/README.md @@ -0,0 +1,45 @@ +## Pcli (Prysm CLI) + +This is a utility to help users perform eth2 specific commands. + +### Usage + +*Name:* + **pcli** - A command line utility to run eth2 specific commands + +*Usage:* + pcli [global options] command [command options] [arguments...] + +*Commands:* + help, h Shows a list of commands or help for one command + state-transition: + state-transition Subcommand to run manual state transitions + + +*Flags:* + --help, -h show help (default: false) + --version, -v print the version (default: false) + +*State Transition Subcommand:* + pcli state-transition - Subcommand to run manual state transitions + +*State Transition Usage:*: + pcli state-transition [command options] [arguments...] + + +*State Transition Flags:* + --block-path value Path to block file(ssz) + --pre-state-patch value Path to pre state file(ssz) + --expected-post-state-path value Path to expected post state file(ssz) + --help, -h show help (default: false) + + + +### Example + +To use pcli manual state transition: + +``` +bazel run //tools/pcli:pcli -- state-transition --block-path /path/to/block.ssz --pre-state-path /path/to/state.ssz +``` + diff --git a/tools/pcli/main.go b/tools/pcli/main.go new file mode 100644 index 000000000000..7a80120da8a2 --- /dev/null +++ b/tools/pcli/main.go @@ -0,0 +1,149 @@ +package main + +import ( + "bufio" + "context" + "fmt" + "io/ioutil" + "os" + "strings" + + ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/prysmaticlabs/go-ssz" + "github.com/prysmaticlabs/prysm/beacon-chain/core/state" + stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/version" + log "github.com/sirupsen/logrus" + prefixed "github.com/x-cray/logrus-prefixed-formatter" + "gopkg.in/d4l3k/messagediff.v1" + "gopkg.in/urfave/cli.v2" +) + +func main() { + var blockPath string + var preStatePath string + var expectedPostStatePath string + + customFormatter := new(prefixed.TextFormatter) + customFormatter.TimestampFormat = "2006-01-02 15:04:05" + customFormatter.FullTimestamp = true + log.SetFormatter(customFormatter) + + app := cli.App{} + app.Name = "pcli" + app.Usage = "A command line utility to run eth2 specific commands" + app.Version = version.GetVersion() + app.Commands = []*cli.Command{{ + Name: "state-transition", + Category: "state-transition", + Usage: "Subcommand to run manual state transitions", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "block-path", + Usage: "Path to block file(ssz)", + Destination: &blockPath, + }, + &cli.StringFlag{ + Name: "pre-state-path", + Usage: "Path to pre state file(ssz)", + Destination: &preStatePath, + }, + &cli.StringFlag{ + Name: "expected-post-state-path", + Usage: "Path to expected post state file(ssz)", + Destination: &expectedPostStatePath, + }, + }, + Action: func(c *cli.Context) error { + if blockPath == "" { + log.Info("Block path not provided for state transition. " + + "Please provide path") + reader := bufio.NewReader(os.Stdin) + text, err := reader.ReadString('\n') + if err != nil { + log.Fatal(err) + } + if text = strings.Replace(text, "\n", "", -1); text == "" { + log.Fatal("Empty block path given") + } + blockPath = text + } + block := ðpb.SignedBeaconBlock{} + if err := dataFetcher(blockPath, block); err != nil { + log.Fatal(err) + } + blkRoot, err := stateutil.BlockRoot(block.Block) + if err != nil { + log.Fatal(err) + } + if preStatePath == "" { + log.Info("Pre State path not provided for state transition. " + + "Please provide path") + reader := bufio.NewReader(os.Stdin) + text, err := reader.ReadString('\n') + if err != nil { + log.Fatal(err) + } + if text = strings.Replace(text, "\n", "", -1); text == "" { + log.Fatal("Empty state path given") + } + preStatePath = text + } + preState := &pb.BeaconState{} + if err := dataFetcher(preStatePath, preState); err != nil { + log.Fatal(err) + } + stateObj, err := stateTrie.InitializeFromProto(preState) + if err != nil { + log.Fatal(err) + } + preStateRoot, err := stateObj.HashTreeRoot(context.Background()) + if err != nil { + log.Fatal(err) + } + log.WithFields(log.Fields{ + "blockSlot": fmt.Sprintf("%d", block.Block.Slot), + "preStateSlot": fmt.Sprintf("%d", stateObj.Slot()), + }).Infof( + "Performing state transition with a block root of %#x and pre state root of %#x", + blkRoot, + preStateRoot, + ) + postState, err := state.ExecuteStateTransition(context.Background(), stateObj, block) + if err != nil { + log.Fatal(err) + } + postRoot, err := postState.HashTreeRoot(context.Background()) + log.Infof("Finished state transition with post state root of %#x", postRoot) + + // Diff the state if a post state is provided. + if expectedPostStatePath != "" { + expectedState := &pb.BeaconState{} + if err := dataFetcher(expectedPostStatePath, expectedState); err != nil { + log.Fatal(err) + } + if !ssz.DeepEqual(expectedState, postState.InnerStateUnsafe()) { + diff, _ := messagediff.PrettyDiff(expectedState, postState.InnerStateUnsafe()) + log.Errorf("Derived state differs from provided post state: %s", diff) + } + } + return nil + }, + }, + } + if err := app.Run(os.Args); err != nil { + log.Error(err.Error()) + os.Exit(1) + } +} + +// dataFetcher fetches and unmarshals data from file to provided data structure. +func dataFetcher(fPath string, data interface{}) error { + rawFile, err := ioutil.ReadFile(fPath) + if err != nil { + return err + } + return ssz.Unmarshal(rawFile, data) +}