From fc05e306dd3d739e57d223e32d582ee827a4028a Mon Sep 17 00:00:00 2001 From: Nishant Das Date: Thu, 18 Jan 2024 22:44:06 +0800 Subject: [PATCH] Allow Pcli to Run State Transitions Easily (#13484) * add all this in * gaz * add flag --- encoding/ssz/detect/configfork.go | 21 +++++- encoding/ssz/detect/configfork_test.go | 91 ++++++++++++++++++++++++++ tools/pcli/BUILD.bazel | 6 +- tools/pcli/main.go | 91 ++++++++++++++++++++------ 4 files changed, 187 insertions(+), 22 deletions(-) diff --git a/encoding/ssz/detect/configfork.go b/encoding/ssz/detect/configfork.go index 79f48fea41..3238bc86b3 100644 --- a/encoding/ssz/detect/configfork.go +++ b/encoding/ssz/detect/configfork.go @@ -49,6 +49,23 @@ func FromState(marshaled []byte) (*VersionedUnmarshaler, error) { return FromForkVersion(cv) } +// FromBlock uses the known size of an offset and signature to determine the slot of a block without unmarshalling it. +// The slot is used to determine the version along with the respective config. +func FromBlock(marshaled []byte) (*VersionedUnmarshaler, error) { + slot, err := slotFromBlock(marshaled) + if err != nil { + return nil, err + } + copiedCfg := params.BeaconConfig().Copy() + epoch := slots.ToEpoch(slot) + fs := forks.NewOrderedSchedule(copiedCfg) + ver, err := fs.VersionForEpoch(epoch) + if err != nil { + return nil, err + } + return FromForkVersion(ver) +} + var ErrForkNotFound = errors.New("version found in fork schedule but can't be matched to a named fork") // FromForkVersion uses a lookup table to resolve a Version (from a beacon node api for instance, or obtained by peeking at @@ -162,7 +179,7 @@ var errBlockForkMismatch = errors.New("fork or config detected in unmarshaler is // UnmarshalBeaconBlock uses internal knowledge in the VersionedUnmarshaler to pick the right concrete ReadOnlySignedBeaconBlock type, // then Unmarshal()s the type and returns an instance of block.ReadOnlySignedBeaconBlock if successful. -func (cf *VersionedUnmarshaler) UnmarshalBeaconBlock(marshaled []byte) (interfaces.ReadOnlySignedBeaconBlock, error) { +func (cf *VersionedUnmarshaler) UnmarshalBeaconBlock(marshaled []byte) (interfaces.SignedBeaconBlock, error) { slot, err := slotFromBlock(marshaled) if err != nil { return nil, err @@ -197,7 +214,7 @@ func (cf *VersionedUnmarshaler) UnmarshalBeaconBlock(marshaled []byte) (interfac // UnmarshalBlindedBeaconBlock uses internal knowledge in the VersionedUnmarshaler to pick the right concrete blinded ReadOnlySignedBeaconBlock type, // then Unmarshal()s the type and returns an instance of block.ReadOnlySignedBeaconBlock if successful. // For Phase0 and Altair it works exactly line UnmarshalBeaconBlock. -func (cf *VersionedUnmarshaler) UnmarshalBlindedBeaconBlock(marshaled []byte) (interfaces.ReadOnlySignedBeaconBlock, error) { +func (cf *VersionedUnmarshaler) UnmarshalBlindedBeaconBlock(marshaled []byte) (interfaces.SignedBeaconBlock, error) { slot, err := slotFromBlock(marshaled) if err != nil { return nil, err diff --git a/encoding/ssz/detect/configfork_test.go b/encoding/ssz/detect/configfork_test.go index 6f3c4f4dd8..e0d13079d7 100644 --- a/encoding/ssz/detect/configfork_test.go +++ b/encoding/ssz/detect/configfork_test.go @@ -204,6 +204,97 @@ func TestUnmarshalState(t *testing.T) { } } +func TestDetectAndUnmarshalBlock(t *testing.T) { + undo := util.HackDenebMaxuint(t) + defer undo() + altairS, err := slots.EpochStart(params.BeaconConfig().AltairForkEpoch) + require.NoError(t, err) + bellaS, err := slots.EpochStart(params.BeaconConfig().BellatrixForkEpoch) + require.NoError(t, err) + capellaS, err := slots.EpochStart(params.BeaconConfig().CapellaForkEpoch) + require.NoError(t, err) + denebS, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch) + require.NoError(t, err) + cases := []struct { + b func(*testing.T, primitives.Slot) interfaces.ReadOnlySignedBeaconBlock + name string + slot primitives.Slot + errExists bool + }{ + { + name: "genesis - slot 0", + b: signedTestBlockGenesis, + }, + { + name: "last slot of phase 0", + b: signedTestBlockGenesis, + slot: altairS - 1, + }, + { + name: "first slot of altair", + b: signedTestBlockAltair, + slot: altairS, + }, + { + name: "last slot of altair", + b: signedTestBlockAltair, + slot: bellaS - 1, + }, + { + name: "first slot of bellatrix", + b: signedTestBlockBellatrix, + slot: bellaS, + }, + { + name: "first slot of capella", + b: signedTestBlockCapella, + slot: capellaS, + }, + { + name: "last slot of capella", + b: signedTestBlockCapella, + slot: denebS - 1, + }, + { + name: "first slot of deneb", + b: signedTestBlockDeneb, + slot: denebS, + }, + { + name: "bellatrix block in altair slot", + b: signedTestBlockBellatrix, + slot: bellaS - 1, + errExists: true, + }, + { + name: "genesis block in altair slot", + b: signedTestBlockGenesis, + slot: bellaS - 1, + errExists: true, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + b := c.b(t, c.slot) + marshaled, err := b.MarshalSSZ() + require.NoError(t, err) + cf, err := FromBlock(marshaled) + require.NoError(t, err) + bcf, err := cf.UnmarshalBeaconBlock(marshaled) + if c.errExists { + require.NotNil(t, err) + return + } + require.NoError(t, err) + expected, err := b.Block().HashTreeRoot() + require.NoError(t, err) + actual, err := bcf.Block().HashTreeRoot() + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + } +} + func TestUnmarshalBlock(t *testing.T) { undo := util.HackDenebMaxuint(t) defer undo() diff --git a/tools/pcli/BUILD.bazel b/tools/pcli/BUILD.bazel index 583d8d9e59..0f503329a3 100644 --- a/tools/pcli/BUILD.bazel +++ b/tools/pcli/BUILD.bazel @@ -9,13 +9,17 @@ go_library( visibility = ["//visibility:private"], deps = [ "//beacon-chain/core/transition:go_default_library", + "//beacon-chain/state:go_default_library", "//beacon-chain/state/state-native:go_default_library", - "//consensus-types/blocks:go_default_library", + "//config/params:go_default_library", + "//consensus-types/interfaces:go_default_library", + "//encoding/ssz/detect:go_default_library", "//encoding/ssz/equality:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//runtime/logging/logrus-prefixed-formatter:go_default_library", "//runtime/version:go_default_library", "@com_github_kr_pretty//:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@com_github_prysmaticlabs_fastssz//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@com_github_urfave_cli_v2//:go_default_library", diff --git a/tools/pcli/main.go b/tools/pcli/main.go index 3a9d3b5455..9f3169d44c 100644 --- a/tools/pcli/main.go +++ b/tools/pcli/main.go @@ -11,10 +11,14 @@ import ( "time" "github.com/kr/pretty" + "github.com/pkg/errors" fssz "github.com/prysmaticlabs/fastssz" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/state" state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native" - "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks" + "github.com/prysmaticlabs/prysm/v4/config/params" + "github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces" + "github.com/prysmaticlabs/prysm/v4/encoding/ssz/detect" "github.com/prysmaticlabs/prysm/v4/encoding/ssz/equality" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" prefixed "github.com/prysmaticlabs/prysm/v4/runtime/logging/logrus-prefixed-formatter" @@ -28,6 +32,7 @@ func main() { var blockPath string var preStatePath string var expectedPostStatePath string + var network string var sszPath string var sszType string @@ -157,8 +162,36 @@ func main() { Usage: "Path to expected post state file(ssz)", Destination: &expectedPostStatePath, }, + &cli.StringFlag{ + Name: "network", + Usage: "Network to run the state transition in", + Destination: &network, + }, }, Action: func(c *cli.Context) error { + if network != "" { + switch network { + case params.PraterName: + if err := params.SetActive(params.PraterConfig()); err != nil { + log.Fatal(err) + } + case params.GoerliName: + if err := params.SetActive(params.PraterConfig()); err != nil { + log.Fatal(err) + } + case params.SepoliaName: + if err := params.SetActive(params.SepoliaConfig()); err != nil { + log.Fatal(err) + } + case params.HoleskyName: + if err := params.SetActive(params.HoleskyConfig()); err != nil { + log.Fatal(err) + } + default: + log.Fatalf("Unknown network provided: %s", network) + } + } + if blockPath == "" { log.Info("Block path not provided for state transition. " + "Please provide path") @@ -172,11 +205,11 @@ func main() { } blockPath = text } - block := ðpb.SignedBeaconBlock{} - if err := dataFetcher(blockPath, block); err != nil { + block, err := detectBlock(blockPath) + if err != nil { log.Fatal(err) } - blkRoot, err := block.Block.HashTreeRoot() + blkRoot, err := block.Block().HashTreeRoot() if err != nil { log.Fatal(err) } @@ -193,11 +226,7 @@ func main() { } preStatePath = text } - preState := ðpb.BeaconState{} - if err := dataFetcher(preStatePath, preState); err != nil { - log.Fatal(err) - } - stateObj, err := state_native.InitializeFromProtoPhase0(preState) + stateObj, err := detectState(preStatePath) if err != nil { log.Fatal(err) } @@ -206,18 +235,14 @@ func main() { log.Fatal(err) } log.WithFields(log.Fields{ - "blockSlot": fmt.Sprintf("%d", block.Block.Slot), + "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, ) - wsb, err := blocks.NewSignedBeaconBlock(block) - if err != nil { - log.Fatal(err) - } - postState, err := transition.ExecuteStateTransition(context.Background(), stateObj, wsb) + postState, err := transition.ExecuteStateTransition(context.Background(), stateObj, block) if err != nil { log.Fatal(err) } @@ -229,12 +254,12 @@ func main() { // Diff the state if a post state is provided. if expectedPostStatePath != "" { - expectedState := ðpb.BeaconState{} - if err := dataFetcher(expectedPostStatePath, expectedState); err != nil { + expectedState, err := detectState(expectedPostStatePath) + if err != nil { log.Fatal(err) } - if !equality.DeepEqual(expectedState, postState.ToProtoUnsafe()) { - diff, _ := messagediff.PrettyDiff(expectedState, postState.ToProtoUnsafe()) + if !equality.DeepEqual(expectedState.ToProtoUnsafe(), postState.ToProtoUnsafe()) { + diff, _ := messagediff.PrettyDiff(expectedState.ToProtoUnsafe(), postState.ToProtoUnsafe()) log.Errorf("Derived state differs from provided post state: %s", diff) } } @@ -257,6 +282,34 @@ func dataFetcher(fPath string, data fssz.Unmarshaler) error { return data.UnmarshalSSZ(rawFile) } +func detectState(fPath string) (state.BeaconState, error) { + rawFile, err := os.ReadFile(fPath) // #nosec G304 + if err != nil { + return nil, err + } + vu, err := detect.FromState(rawFile) + if err != nil { + return nil, errors.Wrap(err, "error detecting state from file") + } + s, err := vu.UnmarshalBeaconState(rawFile) + if err != nil { + return nil, errors.Wrap(err, "error unmarshalling state") + } + return s, nil +} + +func detectBlock(fPath string) (interfaces.SignedBeaconBlock, error) { + rawFile, err := os.ReadFile(fPath) // #nosec G304 + if err != nil { + return nil, err + } + vu, err := detect.FromBlock(rawFile) + if err != nil { + return nil, err + } + return vu.UnmarshalBeaconBlock(rawFile) +} + func prettyPrint(sszPath string, data fssz.Unmarshaler) { if err := dataFetcher(sszPath, data); err != nil { log.Fatal(err)