Files
prysm/testing/endtoend/components/beacon_node.go
kasey 2e49fdb3d2 Start chain from bellatrix state (#11746)
* WIP trying to start from bellatrix state

* env var to control log path with unique paths

due to flaky test re-run behavior, logs from a failed test run are
overwritten by subsequent retries. This makes it difficult to retrieve
logs after the first failed run. It also takes some squinting through
output to find the location of the log file in the first place. This
flag enables logs to be placed in an arbitrary path. Note that bazel
sandboxing generally will force this path to be in the /tmp tree.

* WIP - grabbing changes from rm-pre-genesis branch

* combine bellatrix state w/ rm-pre-genesis branch

* WIP

* use encoding/detect for genesis state bytes

* WIP more fixes towards start from bellatrix

* remove debug wrapping

* WIP

* multiple bugfixes

* fix fork ordering bug and bellatrix genesis blocks

* send deposits, spam tx to advance, fix miner alloc

* WIP

* WIP mess

* WIP

* Print process ID information for purposes of attaching a debugger

* bugs: genesis body_root and deposit index mismatch

* fix voting period start, skip altair check

* add changes

* make it better

* rm startup FCU, rm logs

* cleanup import grouping&ordering

* restore FCU log, get rid of tmp var

* rm newline

* restore newline

* restore wrapped error

* rm newline

* removing boot node version override

this doesn't seem to matter?

* add issue number to todo comment

* rm commented code

* rm vmdebug geth flag

* unexport values only used with genesis test pkg

and add comments where missing from exported values.

* adding comments to special cases for testnets

* migrate comments from PR to actual code :)

* rm unused test param

* mark e2e spawns exempt from gosec warning

* Fix DeepSource errors in `proposer_bellatrix.go` (#11739)

* Fix DeepSource errors in

* Omit receiver name

* Address PR comments

* Remove unused variable

* Fix more DeepSource errors

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Remove `Test_IsExecutionEnabledCapella` (#11752)

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Add REST implementation for Validator's `ProposeBeaconBlock` (#11731)

* WIP

* WIP

* WIP

* Add tests

* WIP

* Add more tests

* Address DeepSource errors

* Remove unused param

* Add more tests

* Address PR comments

* Address PR comments

* Fix formatting

* Remove unused parameter

* Fix TestLittleEndianBytesToBigInt

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* fix validator client (#11755)

* fix validator client

(cherry picked from commit deb138959a)

* Use signed changes in middleware block

Co-authored-by: Potuz <potuz@prysmaticlabs.com>

* API `finalized` metadata field - update protos (#11749)

* API `finalized` metadata field - update protos

* change nums

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>

* log breaks unit tests that don't do full arg setup

easiest to just remove it for now

* restore prior behavior of phase0 block for altair

* update unit tests to account for special case

* loosen condition for fork version to match config

we don't know which fork version genesis will start from, so we
shouldn't force it to be a phase0 genesis.

* skip until we can mod configs at runtime

* NewGenesisBlockForState computes state root itself

* rm noisy log

* this log would be noisy in mainnet

* fix format specifier, []byte -> string

* core.Genesis UnmarshalJson has a value receiver :)

* no longer needs to be exported

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
Co-authored-by: prestonvanloon <preston@prysmaticlabs.com>
Co-authored-by: nisdas <nishdas93@gmail.com>
Co-authored-by: Patrice Vignola <vignola.patrice@gmail.com>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: terencechain <terence@prysmaticlabs.com>
Co-authored-by: Potuz <potuz@prysmaticlabs.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2022-12-14 07:13:49 +08:00

416 lines
14 KiB
Go

// Package components defines utilities to spin up actual
// beacon node and validator processes as needed by end to end tests.
package components
import (
"context"
"fmt"
"os"
"os/exec"
"path"
"strconv"
"strings"
"syscall"
"github.com/bazelbuild/rules_go/go/tools/bazel"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/blocks"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state"
state_native "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/state-native"
cmdshared "github.com/prysmaticlabs/prysm/v3/cmd"
"github.com/prysmaticlabs/prysm/v3/cmd/beacon-chain/flags"
"github.com/prysmaticlabs/prysm/v3/cmd/beacon-chain/sync/genesis"
"github.com/prysmaticlabs/prysm/v3/config/features"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/config/params"
"github.com/prysmaticlabs/prysm/v3/container/trie"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v3/io/file"
enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v3/runtime/interop"
"github.com/prysmaticlabs/prysm/v3/testing/endtoend/helpers"
e2e "github.com/prysmaticlabs/prysm/v3/testing/endtoend/params"
e2etypes "github.com/prysmaticlabs/prysm/v3/testing/endtoend/types"
)
var _ e2etypes.ComponentRunner = (*BeaconNode)(nil)
var _ e2etypes.ComponentRunner = (*BeaconNodeSet)(nil)
var _ e2etypes.MultipleComponentRunners = (*BeaconNodeSet)(nil)
var _ e2etypes.BeaconNodeSet = (*BeaconNodeSet)(nil)
// BeaconNodeSet represents set of beacon nodes.
type BeaconNodeSet struct {
e2etypes.ComponentRunner
config *e2etypes.E2EConfig
nodes []e2etypes.ComponentRunner
enr string
ids []string
started chan struct{}
}
// SetENR assigns ENR to the set of beacon nodes.
func (s *BeaconNodeSet) SetENR(enr string) {
s.enr = enr
}
// NewBeaconNodes creates and returns a set of beacon nodes.
func NewBeaconNodes(config *e2etypes.E2EConfig) *BeaconNodeSet {
return &BeaconNodeSet{
config: config,
started: make(chan struct{}, 1),
}
}
// Start starts all the beacon nodes in set.
func (s *BeaconNodeSet) Start(ctx context.Context) error {
if s.enr == "" {
return errors.New("empty ENR")
}
// Create beacon nodes.
nodes := make([]e2etypes.ComponentRunner, e2e.TestParams.BeaconNodeCount)
for i := 0; i < e2e.TestParams.BeaconNodeCount; i++ {
nodes[i] = NewBeaconNode(s.config, i, s.enr)
}
s.nodes = nodes
// Wait for all nodes to finish their job (blocking).
// Once nodes are ready passed in handler function will be called.
return helpers.WaitOnNodes(ctx, nodes, func() {
if s.config.UseFixedPeerIDs {
for i := 0; i < len(nodes); i++ {
s.ids = append(s.ids, nodes[i].(*BeaconNode).peerID)
}
s.config.PeerIDs = s.ids
}
// All nodes started, close channel, so that all services waiting on a set, can proceed.
close(s.started)
})
}
// Started checks whether beacon node set is started and all nodes are ready to be queried.
func (s *BeaconNodeSet) Started() <-chan struct{} {
return s.started
}
// Pause pauses the component and its underlying process.
func (s *BeaconNodeSet) Pause() error {
for _, n := range s.nodes {
if err := n.Pause(); err != nil {
return err
}
}
return nil
}
// Resume resumes the component and its underlying process.
func (s *BeaconNodeSet) Resume() error {
for _, n := range s.nodes {
if err := n.Resume(); err != nil {
return err
}
}
return nil
}
// Stop stops the component and its underlying process.
func (s *BeaconNodeSet) Stop() error {
for _, n := range s.nodes {
if err := n.Stop(); err != nil {
return err
}
}
return nil
}
// PauseAtIndex pauses the component and its underlying process at the desired index.
func (s *BeaconNodeSet) PauseAtIndex(i int) error {
if i >= len(s.nodes) {
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.nodes))
}
return s.nodes[i].Pause()
}
// ResumeAtIndex resumes the component and its underlying process at the desired index.
func (s *BeaconNodeSet) ResumeAtIndex(i int) error {
if i >= len(s.nodes) {
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.nodes))
}
return s.nodes[i].Resume()
}
// StopAtIndex stops the component and its underlying process at the desired index.
func (s *BeaconNodeSet) StopAtIndex(i int) error {
if i >= len(s.nodes) {
return errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.nodes))
}
return s.nodes[i].Stop()
}
// ComponentAtIndex returns the component at the provided index.
func (s *BeaconNodeSet) ComponentAtIndex(i int) (e2etypes.ComponentRunner, error) {
if i >= len(s.nodes) {
return nil, errors.Errorf("provided index exceeds slice size: %d >= %d", i, len(s.nodes))
}
return s.nodes[i], nil
}
// BeaconNode represents beacon node.
type BeaconNode struct {
e2etypes.ComponentRunner
config *e2etypes.E2EConfig
started chan struct{}
index int
enr string
peerID string
cmd *exec.Cmd
}
// NewBeaconNode creates and returns a beacon node.
func NewBeaconNode(config *e2etypes.E2EConfig, index int, enr string) *BeaconNode {
return &BeaconNode{
config: config,
index: index,
enr: enr,
started: make(chan struct{}, 1),
}
}
func (node *BeaconNode) generateGenesis(ctx context.Context) (state.BeaconState, error) {
if e2e.TestParams.Eth1GenesisBlock == nil {
return nil, errors.New("Cannot construct bellatrix block, e2e.TestParams.Eth1GenesisBlock == nil")
}
gb := e2e.TestParams.Eth1GenesisBlock
// so the DepositRoot in the BeaconState should be set to the HTR of an empty deposit trie.
t, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
if err != nil {
return nil, err
}
dr, err := t.HashTreeRoot()
if err != nil {
return nil, err
}
e1d := &ethpb.Eth1Data{
DepositRoot: dr[:],
DepositCount: 0,
BlockHash: gb.Hash().Bytes(),
}
payload := &enginev1.ExecutionPayload{
ParentHash: gb.ParentHash().Bytes(),
FeeRecipient: gb.Coinbase().Bytes(),
StateRoot: gb.Root().Bytes(),
ReceiptsRoot: gb.ReceiptHash().Bytes(),
LogsBloom: gb.Bloom().Bytes(),
PrevRandao: params.BeaconConfig().ZeroHash[:],
BlockNumber: gb.NumberU64(),
GasLimit: gb.GasLimit(),
GasUsed: gb.GasUsed(),
Timestamp: gb.Time(),
ExtraData: gb.Extra()[:32],
BaseFeePerGas: bytesutil.PadTo(bytesutil.ReverseByteOrder(gb.BaseFee().Bytes()), fieldparams.RootLength),
BlockHash: gb.Hash().Bytes(),
Transactions: make([][]byte, 0),
}
genesis, _, err := interop.GenerateGenesisStateBellatrix(ctx, e2e.TestParams.CLGenesisTime, params.BeaconConfig().MinGenesisActiveValidatorCount, payload, e1d)
if err != nil {
return nil, err
}
lbhr, err := genesis.LatestBlockHeader.HashTreeRoot()
if err != nil {
return nil, err
}
si, err := state_native.InitializeFromProtoUnsafeBellatrix(genesis)
if err != nil {
return nil, err
}
genb, err := blocks.NewGenesisBlockForState(ctx, si)
if err != nil {
return nil, err
}
gbr, err := genb.Block().HashTreeRoot()
if err != nil {
return nil, err
}
log.WithField("el_block_time", gb.Time()).
WithField("cl_genesis_time", genesis.GenesisTime).
WithField("state_root", fmt.Sprintf("%#x", genb.Block().StateRoot())).
WithField("latest_block_header_root", fmt.Sprintf("%#x", lbhr)).
WithField("latest_block_header_state_root", fmt.Sprintf("%#x", genesis.LatestBlockHeader.StateRoot)).
WithField("latest_block_header_parent_root", fmt.Sprintf("%#x", genesis.LatestBlockHeader.ParentRoot)).
WithField("latest_block_header_body_root", fmt.Sprintf("%#x", genesis.LatestBlockHeader.BodyRoot)).
WithField("derived_block_root", fmt.Sprintf("%#x", gbr)).
WithField("el_block_root", fmt.Sprintf("%#x", genesis.Eth1Data.BlockHash)).
Info("genesis eth1 data")
return si, nil
}
func (node *BeaconNode) saveGenesis(ctx context.Context) (string, error) {
// The deposit contract starts with an empty trie, we use the BeaconState to "pre-mine" the validator registry,
g, err := node.generateGenesis(ctx)
if err != nil {
return "", err
}
root, err := g.HashTreeRoot(ctx)
if err != nil {
return "", err
}
lbhr, err := g.LatestBlockHeader().HashTreeRoot()
if err != nil {
return "", err
}
log.WithField("fork_version", g.Fork().CurrentVersion).
WithField("latest_block_header.root", fmt.Sprintf("%#x", lbhr)).
WithField("state_root", fmt.Sprintf("%#x", root)).
Infof("BeaconState info")
genesisBytes, err := g.MarshalSSZ()
if err != nil {
return "", err
}
genesisDir := path.Join(e2e.TestParams.TestPath, fmt.Sprintf("genesis/%d", node.index))
if err := file.MkdirAll(genesisDir); err != nil {
return "", err
}
genesisPath := path.Join(genesisDir, "genesis.ssz")
return genesisPath, file.WriteFile(genesisPath, genesisBytes)
}
// Start starts a fresh beacon node, connecting to all passed in beacon nodes.
func (node *BeaconNode) Start(ctx context.Context) error {
binaryPath, found := bazel.FindBinary("cmd/beacon-chain", "beacon-chain")
if !found {
log.Info(binaryPath)
return errors.New("beacon chain binary not found")
}
config, index, enr := node.config, node.index, node.enr
stdOutFile, err := helpers.DeleteAndCreateFile(e2e.TestParams.LogPath, fmt.Sprintf(e2e.BeaconNodeLogFileName, index))
if err != nil {
return err
}
expectedNumOfPeers := e2e.TestParams.BeaconNodeCount + e2e.TestParams.LighthouseBeaconNodeCount - 1
if node.config.TestSync {
expectedNumOfPeers += 1
}
if node.config.TestCheckpointSync {
expectedNumOfPeers += 1
}
jwtPath := path.Join(e2e.TestParams.TestPath, "eth1data/"+strconv.Itoa(node.index)+"/")
if index == 0 {
jwtPath = path.Join(e2e.TestParams.TestPath, "eth1data/miner/")
}
jwtPath = path.Join(jwtPath, "geth/jwtsecret")
genesisPath, err := node.saveGenesis(ctx)
if err != nil {
return err
}
args := []string{
fmt.Sprintf("--%s=%s", genesis.StatePath.Name, genesisPath),
fmt.Sprintf("--%s=%s/eth2-beacon-node-%d", cmdshared.DataDirFlag.Name, e2e.TestParams.TestPath, index),
fmt.Sprintf("--%s=%s", cmdshared.LogFileName.Name, stdOutFile.Name()),
fmt.Sprintf("--%s=%s", flags.DepositContractFlag.Name, params.BeaconConfig().DepositContractAddress),
fmt.Sprintf("--%s=%d", flags.RPCPort.Name, e2e.TestParams.Ports.PrysmBeaconNodeRPCPort+index),
fmt.Sprintf("--%s=http://127.0.0.1:%d", flags.ExecutionEngineEndpoint.Name, e2e.TestParams.Ports.Eth1ProxyPort+index),
fmt.Sprintf("--%s=%s", flags.ExecutionJWTSecretFlag.Name, jwtPath),
fmt.Sprintf("--%s=%d", flags.MinSyncPeers.Name, 1),
fmt.Sprintf("--%s=%d", cmdshared.P2PUDPPort.Name, e2e.TestParams.Ports.PrysmBeaconNodeUDPPort+index),
fmt.Sprintf("--%s=%d", cmdshared.P2PTCPPort.Name, e2e.TestParams.Ports.PrysmBeaconNodeTCPPort+index),
fmt.Sprintf("--%s=%d", cmdshared.P2PMaxPeers.Name, expectedNumOfPeers),
fmt.Sprintf("--%s=%d", flags.MonitoringPortFlag.Name, e2e.TestParams.Ports.PrysmBeaconNodeMetricsPort+index),
fmt.Sprintf("--%s=%d", flags.GRPCGatewayPort.Name, e2e.TestParams.Ports.PrysmBeaconNodeGatewayPort+index),
fmt.Sprintf("--%s=%d", flags.ContractDeploymentBlock.Name, 0),
fmt.Sprintf("--%s=%d", flags.MinPeersPerSubnet.Name, 0),
fmt.Sprintf("--%s=%d", cmdshared.RPCMaxPageSizeFlag.Name, params.BeaconConfig().MinGenesisActiveValidatorCount),
fmt.Sprintf("--%s=%s", cmdshared.BootstrapNode.Name, enr),
fmt.Sprintf("--%s=%s", cmdshared.VerbosityFlag.Name, "debug"),
fmt.Sprintf("--%s=%d", flags.BlockBatchLimitBurstFactor.Name, 8),
"--" + cmdshared.ForceClearDB.Name,
"--" + cmdshared.E2EConfigFlag.Name,
"--" + cmdshared.AcceptTosFlag.Name,
"--" + flags.EnableDebugRPCEndpoints.Name,
}
if config.UsePprof {
args = append(args, "--pprof", fmt.Sprintf("--pprofport=%d", e2e.TestParams.Ports.PrysmBeaconNodePprofPort+index))
}
// Only add in the feature flags if we either aren't performing a control test
// on our features or the beacon index is a multiplier of 2 (idea is to split nodes
// equally down the line with one group having feature flags and the other without
// feature flags; this is to allow A-B testing on new features)
if !config.TestFeature || index%2 == 0 {
args = append(args, features.E2EBeaconChainFlags...)
}
args = append(args, config.BeaconFlags...)
cmd := exec.CommandContext(ctx, binaryPath, args...) // #nosec G204 -- Safe
// Write stdout and stderr to log files.
stdout, err := os.Create(path.Join(e2e.TestParams.LogPath, fmt.Sprintf("beacon_node_%d_stdout.log", index)))
if err != nil {
return err
}
stderr, err := os.Create(path.Join(e2e.TestParams.LogPath, fmt.Sprintf("beacon_node_%d_stderr.log", index)))
if err != nil {
return err
}
defer func() {
if err := stdout.Close(); err != nil {
log.WithError(err).Error("Failed to close stdout file")
}
if err := stderr.Close(); err != nil {
log.WithError(err).Error("Failed to close stderr file")
}
}()
cmd.Stdout = stdout
cmd.Stderr = stderr
log.Infof("Starting beacon chain %d with flags: %s", index, strings.Join(args[2:], " "))
if err = cmd.Start(); err != nil {
return fmt.Errorf("failed to start beacon node: %w", err)
}
if err = helpers.WaitForTextInFile(stdOutFile, "gRPC server listening on port"); err != nil {
return fmt.Errorf("could not find multiaddr for node %d, this means the node had issues starting: %w", index, err)
}
if config.UseFixedPeerIDs {
peerId, err := helpers.FindFollowingTextInFile(stdOutFile, "Running node with peer id of ")
if err != nil {
return fmt.Errorf("could not find peer id: %w", err)
}
node.peerID = peerId
}
// Mark node as ready.
close(node.started)
node.cmd = cmd
return cmd.Wait()
}
// Started checks whether beacon node is started and ready to be queried.
func (node *BeaconNode) Started() <-chan struct{} {
return node.started
}
// Pause pauses the component and its underlying process.
func (node *BeaconNode) Pause() error {
return node.cmd.Process.Signal(syscall.SIGSTOP)
}
// Resume resumes the component and its underlying process.
func (node *BeaconNode) Resume() error {
return node.cmd.Process.Signal(syscall.SIGCONT)
}
// Stop stops the component and its underlying process.
func (node *BeaconNode) Stop() error {
return node.cmd.Process.Kill()
}
func (node *BeaconNode) UnderlyingProcess() *os.Process {
return node.cmd.Process
}