2020-04-27 16:47:28 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2020-06-04 21:08:24 +00:00
|
|
|
"regexp"
|
2023-07-20 08:49:17 +00:00
|
|
|
"runtime"
|
2020-04-27 16:47:28 +00:00
|
|
|
"strings"
|
2023-06-12 16:54:43 +00:00
|
|
|
"time"
|
2020-04-27 16:47:28 +00:00
|
|
|
|
2020-06-04 21:08:24 +00:00
|
|
|
"github.com/kr/pretty"
|
2024-01-18 14:44:06 +00:00
|
|
|
"github.com/pkg/errors"
|
2022-06-28 13:03:24 +00:00
|
|
|
fssz "github.com/prysmaticlabs/fastssz"
|
2024-03-12 15:03:21 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epoch/precompute"
|
2024-02-15 05:46:47 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
|
|
|
|
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
|
|
|
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
|
|
|
"github.com/prysmaticlabs/prysm/v5/config/params"
|
|
|
|
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
|
|
|
"github.com/prysmaticlabs/prysm/v5/encoding/ssz/detect"
|
|
|
|
"github.com/prysmaticlabs/prysm/v5/encoding/ssz/equality"
|
|
|
|
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
|
|
|
prefixed "github.com/prysmaticlabs/prysm/v5/runtime/logging/logrus-prefixed-formatter"
|
|
|
|
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
2020-04-27 16:47:28 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2020-05-31 20:08:36 +00:00
|
|
|
"github.com/urfave/cli/v2"
|
2020-04-27 16:47:28 +00:00
|
|
|
"gopkg.in/d4l3k/messagediff.v1"
|
|
|
|
)
|
|
|
|
|
2024-03-12 15:03:21 +00:00
|
|
|
var blockPath string
|
|
|
|
var preStatePath string
|
|
|
|
var expectedPostStatePath string
|
|
|
|
var network string
|
|
|
|
var sszPath string
|
|
|
|
var sszType string
|
|
|
|
var prettyCommand = &cli.Command{
|
|
|
|
Name: "pretty",
|
|
|
|
Aliases: []string{"p"},
|
|
|
|
Usage: "pretty-print SSZ data",
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "ssz-path",
|
|
|
|
Usage: "Path to file(ssz)",
|
|
|
|
Required: true,
|
|
|
|
Destination: &sszPath,
|
|
|
|
},
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "data-type",
|
|
|
|
Usage: "ssz file data type: " +
|
|
|
|
"block|" +
|
|
|
|
"blinded_block|" +
|
|
|
|
"signed_block|" +
|
|
|
|
"attestation|" +
|
|
|
|
"block_header|" +
|
|
|
|
"deposit|" +
|
|
|
|
"proposer_slashing|" +
|
|
|
|
"signed_block_header|" +
|
|
|
|
"signed_voluntary_exit|" +
|
|
|
|
"voluntary_exit|" +
|
|
|
|
"state_capella",
|
|
|
|
Required: true,
|
|
|
|
Destination: &sszType,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
var data fssz.Unmarshaler
|
|
|
|
switch sszType {
|
|
|
|
case "block":
|
|
|
|
data = ðpb.BeaconBlock{}
|
|
|
|
case "signed_block":
|
|
|
|
data = ðpb.SignedBeaconBlock{}
|
|
|
|
case "blinded_block":
|
|
|
|
data = ðpb.BlindedBeaconBlockBellatrix{}
|
|
|
|
case "attestation":
|
|
|
|
data = ðpb.Attestation{}
|
|
|
|
case "block_header":
|
|
|
|
data = ðpb.BeaconBlockHeader{}
|
|
|
|
case "deposit":
|
|
|
|
data = ðpb.Deposit{}
|
|
|
|
case "deposit_message":
|
|
|
|
data = ðpb.DepositMessage{}
|
|
|
|
case "proposer_slashing":
|
|
|
|
data = ðpb.ProposerSlashing{}
|
|
|
|
case "signed_block_header":
|
|
|
|
data = ðpb.SignedBeaconBlockHeader{}
|
|
|
|
case "signed_voluntary_exit":
|
|
|
|
data = ðpb.SignedVoluntaryExit{}
|
|
|
|
case "voluntary_exit":
|
|
|
|
data = ðpb.VoluntaryExit{}
|
|
|
|
case "state_capella":
|
|
|
|
data = ðpb.BeaconStateCapella{}
|
|
|
|
default:
|
|
|
|
log.Fatal("Invalid type")
|
|
|
|
}
|
|
|
|
prettyPrint(sszPath, data)
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
2020-04-27 16:47:28 +00:00
|
|
|
|
2024-03-12 15:03:21 +00:00
|
|
|
var benchmarkHashCommand = &cli.Command{
|
|
|
|
Name: "benchmark-hash",
|
|
|
|
Aliases: []string{"b"},
|
|
|
|
Usage: "benchmark-hash SSZ data",
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "ssz-path",
|
|
|
|
Usage: "Path to file(ssz)",
|
|
|
|
Required: true,
|
|
|
|
Destination: &sszPath,
|
2020-04-27 16:47:28 +00:00
|
|
|
},
|
2024-03-12 15:03:21 +00:00
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "data-type",
|
|
|
|
Usage: "ssz file data type: " +
|
|
|
|
"block_capella|" +
|
|
|
|
"blinded_block_capella|" +
|
|
|
|
"signed_block_capella|" +
|
|
|
|
"attestation|" +
|
|
|
|
"block_header|" +
|
|
|
|
"deposit|" +
|
|
|
|
"proposer_slashing|" +
|
|
|
|
"signed_block_header|" +
|
|
|
|
"signed_voluntary_exit|" +
|
|
|
|
"voluntary_exit|" +
|
|
|
|
"state_capella",
|
|
|
|
Required: true,
|
|
|
|
Destination: &sszType,
|
2023-06-12 16:54:43 +00:00
|
|
|
},
|
2024-03-12 15:03:21 +00:00
|
|
|
},
|
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
benchmarkHash(sszPath, sszType)
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
2024-01-18 14:44:06 +00:00
|
|
|
|
2024-03-12 15:03:21 +00:00
|
|
|
var unrealizedCheckpointsCommand = &cli.Command{
|
|
|
|
Name: "unrealized-checkpoints",
|
|
|
|
Category: "state-computations",
|
|
|
|
Usage: "Subcommand to compute manually the unrealized checkpoints",
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "state-path",
|
|
|
|
Usage: "Path to state file(ssz)",
|
|
|
|
Destination: &preStatePath,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
if preStatePath == "" {
|
|
|
|
log.Info("State path not provided, please provide path")
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
|
|
text, err := reader.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if text = strings.ReplaceAll(text, "\n", ""); text == "" {
|
|
|
|
log.Fatal("Empty state path given")
|
|
|
|
}
|
|
|
|
preStatePath = text
|
|
|
|
}
|
|
|
|
stateObj, err := detectState(preStatePath)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
preStateRoot, err := stateObj.HashTreeRoot(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
log.Infof(
|
|
|
|
"Computing unrealized justification for state at slot %d and root %#x",
|
|
|
|
stateObj.Slot(),
|
|
|
|
preStateRoot,
|
|
|
|
)
|
|
|
|
uj, uf, err := precompute.UnrealizedCheckpoints(stateObj)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
log.Infof("Computed:\nUnrealized Justified: (Root: %#x, Epoch: %d)\nUnrealized Finalized: (Root: %#x, Epoch: %d).", uj.Root, uj.Epoch, uf.Root, uf.Epoch)
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var stateTransitionCommand = &cli.Command{
|
|
|
|
Name: "state-transition",
|
|
|
|
Category: "state-computations",
|
|
|
|
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,
|
|
|
|
},
|
|
|
|
&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 {
|
2020-04-27 16:47:28 +00:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2024-03-12 15:03:21 +00:00
|
|
|
case params.GoerliName:
|
|
|
|
if err := params.SetActive(params.PraterConfig()); err != nil {
|
2020-06-04 21:08:24 +00:00
|
|
|
log.Fatal(err)
|
2020-04-27 16:47:28 +00:00
|
|
|
}
|
2024-03-12 15:03:21 +00:00
|
|
|
case params.SepoliaName:
|
|
|
|
if err := params.SetActive(params.SepoliaConfig()); err != nil {
|
2020-04-27 16:47:28 +00:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2024-03-12 15:03:21 +00:00
|
|
|
case params.HoleskyName:
|
|
|
|
if err := params.SetActive(params.HoleskyConfig()); err != nil {
|
2020-10-01 18:53:36 +00:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2024-03-12 15:03:21 +00:00
|
|
|
default:
|
|
|
|
log.Fatalf("Unknown network provided: %s", network)
|
|
|
|
}
|
|
|
|
}
|
2020-06-04 21:08:24 +00:00
|
|
|
|
2024-03-12 15:03:21 +00:00
|
|
|
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.ReplaceAll(text, "\n", ""); text == "" {
|
|
|
|
log.Fatal("Empty block path given")
|
|
|
|
}
|
|
|
|
blockPath = text
|
|
|
|
}
|
|
|
|
block, err := detectBlock(blockPath)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
blkRoot, err := block.Block().HashTreeRoot()
|
|
|
|
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.ReplaceAll(text, "\n", ""); text == "" {
|
|
|
|
log.Fatal("Empty state path given")
|
|
|
|
}
|
|
|
|
preStatePath = text
|
|
|
|
}
|
|
|
|
stateObj, err := detectState(preStatePath)
|
|
|
|
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 := debugStateTransition(context.Background(), stateObj, block)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
postRoot, err := postState.HashTreeRoot(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
log.Infof("Finished state transition with post state root of %#x", postRoot)
|
|
|
|
|
|
|
|
// Diff the state if a post state is provided.
|
|
|
|
if expectedPostStatePath != "" {
|
|
|
|
expectedState, err := detectState(expectedPostStatePath)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
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 Ethereum consensus specific commands"
|
|
|
|
app.Version = version.Version()
|
|
|
|
app.Commands = []*cli.Command{
|
|
|
|
prettyCommand,
|
|
|
|
benchmarkHashCommand,
|
|
|
|
unrealizedCheckpointsCommand,
|
|
|
|
stateTransitionCommand,
|
2020-04-27 16:47:28 +00:00
|
|
|
}
|
|
|
|
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.
|
2021-01-20 21:03:46 +00:00
|
|
|
func dataFetcher(fPath string, data fssz.Unmarshaler) error {
|
2022-04-18 20:42:07 +00:00
|
|
|
rawFile, err := os.ReadFile(fPath) // #nosec G304
|
2020-04-27 16:47:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-20 21:03:46 +00:00
|
|
|
return data.UnmarshalSSZ(rawFile)
|
2020-04-27 16:47:28 +00:00
|
|
|
}
|
2020-06-04 21:08:24 +00:00
|
|
|
|
2024-01-18 14:44:06 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-01-20 21:03:46 +00:00
|
|
|
func prettyPrint(sszPath string, data fssz.Unmarshaler) {
|
2020-06-04 21:08:24 +00:00
|
|
|
if err := dataFetcher(sszPath, data); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
str := pretty.Sprint(data)
|
|
|
|
re := regexp.MustCompile("(?m)[\r\n]+^.*XXX_.*$")
|
|
|
|
str = re.ReplaceAllString(str, "")
|
|
|
|
fmt.Print(str)
|
|
|
|
}
|
2023-06-12 16:54:43 +00:00
|
|
|
|
|
|
|
func benchmarkHash(sszPath string, sszType string) {
|
|
|
|
switch sszType {
|
|
|
|
case "state_capella":
|
2023-07-15 12:35:58 +00:00
|
|
|
st := ðpb.BeaconStateCapella{}
|
|
|
|
rawFile, err := os.ReadFile(sszPath) // #nosec G304
|
|
|
|
if err != nil {
|
2023-06-12 16:54:43 +00:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2023-07-15 12:35:58 +00:00
|
|
|
|
2023-07-13 12:19:10 +00:00
|
|
|
startDeserialize := time.Now()
|
2023-07-15 12:35:58 +00:00
|
|
|
if err := st.UnmarshalSSZ(rawFile); err != nil {
|
|
|
|
log.Fatal(err)
|
2023-06-12 16:54:43 +00:00
|
|
|
}
|
2023-07-13 12:19:10 +00:00
|
|
|
deserializeDuration := time.Since(startDeserialize)
|
2023-07-15 12:35:58 +00:00
|
|
|
|
2023-07-16 03:39:58 +00:00
|
|
|
stateTrieState, err := state_native.InitializeFromProtoCapella(st)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2023-06-12 16:54:43 +00:00
|
|
|
start := time.Now()
|
2023-07-20 08:49:17 +00:00
|
|
|
stat := &runtime.MemStats{}
|
|
|
|
runtime.ReadMemStats(stat)
|
2023-07-16 03:39:58 +00:00
|
|
|
root, err := stateTrieState.HashTreeRoot(context.Background())
|
2023-06-12 16:54:43 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal("couldn't hash")
|
|
|
|
}
|
2023-07-20 08:49:17 +00:00
|
|
|
newStat := &runtime.MemStats{}
|
|
|
|
runtime.ReadMemStats(newStat)
|
2023-07-13 12:19:10 +00:00
|
|
|
fmt.Printf("Deserialize Duration: %v, Hashing Duration: %v HTR: %#x\n", deserializeDuration, time.Since(start), root)
|
2023-07-20 08:49:17 +00:00
|
|
|
fmt.Printf("Total Memory Allocation Differential: %d bytes, Heap Memory Allocation Differential: %d bytes\n", int64(newStat.TotalAlloc)-int64(stat.TotalAlloc), int64(newStat.HeapAlloc)-int64(stat.HeapAlloc))
|
2023-06-12 16:54:43 +00:00
|
|
|
return
|
|
|
|
default:
|
|
|
|
log.Fatal("Invalid type")
|
|
|
|
}
|
|
|
|
}
|
2024-01-22 14:46:20 +00:00
|
|
|
|
|
|
|
func debugStateTransition(
|
|
|
|
ctx context.Context,
|
|
|
|
st state.BeaconState,
|
|
|
|
signed interfaces.ReadOnlySignedBeaconBlock,
|
|
|
|
) (state.BeaconState, error) {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
parentRoot := signed.Block().ParentRoot()
|
|
|
|
st, err = transition.ProcessSlotsUsingNextSlotCache(ctx, st, parentRoot[:], signed.Block().Slot())
|
|
|
|
if err != nil {
|
|
|
|
return st, errors.Wrap(err, "could not process slots")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute per block transition.
|
|
|
|
set, st, err := transition.ProcessBlockNoVerifyAnySig(ctx, st, signed)
|
|
|
|
if err != nil {
|
|
|
|
return st, errors.Wrap(err, "could not process block")
|
|
|
|
}
|
|
|
|
var valid bool
|
|
|
|
valid, err = set.VerifyVerbosely()
|
|
|
|
if err != nil {
|
|
|
|
return st, errors.Wrap(err, "could not batch verify signature")
|
|
|
|
}
|
|
|
|
if !valid {
|
|
|
|
return st, errors.New("signature in block failed to verify")
|
|
|
|
}
|
|
|
|
return st, nil
|
|
|
|
}
|