github.com/prysmaticlabs/prysm@v1.4.4/endtoend/endtoend_test.go (about)

     1  // Package endtoend performs full a end-to-end test for Prysm,
     2  // including spinning up an ETH1 dev chain, sending deposits to the deposit
     3  // contract, and making sure the beacon node and validators are running and
     4  // performing properly for a few epochs.
     5  package endtoend
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	types "github.com/prysmaticlabs/eth2-types"
    18  	"github.com/prysmaticlabs/prysm/beacon-chain/core/state"
    19  	"github.com/prysmaticlabs/prysm/endtoend/components"
    20  	ev "github.com/prysmaticlabs/prysm/endtoend/evaluators"
    21  	"github.com/prysmaticlabs/prysm/endtoend/helpers"
    22  	e2e "github.com/prysmaticlabs/prysm/endtoend/params"
    23  	e2etypes "github.com/prysmaticlabs/prysm/endtoend/types"
    24  	eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    25  	"github.com/prysmaticlabs/prysm/shared/params"
    26  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    27  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    28  	log "github.com/sirupsen/logrus"
    29  	"golang.org/x/sync/errgroup"
    30  	"google.golang.org/grpc"
    31  	"google.golang.org/protobuf/types/known/emptypb"
    32  )
    33  
    34  const (
    35  	// allNodesStartTimeout defines period after which nodes are considered
    36  	// stalled (safety measure for nodes stuck at startup, shouldn't normally happen).
    37  	allNodesStartTimeout = 5 * time.Minute
    38  )
    39  
    40  func init() {
    41  	state.SkipSlotCache.Disable()
    42  }
    43  
    44  // testRunner abstracts E2E test configuration and running.
    45  type testRunner struct {
    46  	t      *testing.T
    47  	config *e2etypes.E2EConfig
    48  }
    49  
    50  // newTestRunner creates E2E test runner.
    51  func newTestRunner(t *testing.T, config *e2etypes.E2EConfig) *testRunner {
    52  	return &testRunner{
    53  		t:      t,
    54  		config: config,
    55  	}
    56  }
    57  
    58  // run executes configured E2E test.
    59  func (r *testRunner) run() {
    60  	t, config := r.t, r.config
    61  	t.Logf("Shard index: %d\n", e2e.TestParams.TestShardIndex)
    62  	t.Logf("Starting time: %s\n", time.Now().String())
    63  	t.Logf("Log Path: %s\n", e2e.TestParams.LogPath)
    64  
    65  	minGenesisActiveCount := int(params.BeaconConfig().MinGenesisActiveValidatorCount)
    66  
    67  	ctx, done := context.WithCancel(context.Background())
    68  	g, ctx := errgroup.WithContext(ctx)
    69  
    70  	// ETH1 node.
    71  	eth1Node := components.NewEth1Node()
    72  	g.Go(func() error {
    73  		return eth1Node.Start(ctx)
    74  	})
    75  	g.Go(func() error {
    76  		if err := helpers.ComponentsStarted(ctx, []e2etypes.ComponentRunner{eth1Node}); err != nil {
    77  			return fmt.Errorf("sending and mining deposits require ETH1 node to run: %w", err)
    78  		}
    79  		return components.SendAndMineDeposits(eth1Node.KeystorePath(), minGenesisActiveCount, 0, true /* partial */)
    80  	})
    81  
    82  	// Boot node.
    83  	bootNode := components.NewBootNode()
    84  	g.Go(func() error {
    85  		return bootNode.Start(ctx)
    86  	})
    87  
    88  	// Beacon nodes.
    89  	beaconNodes := components.NewBeaconNodes(config)
    90  	g.Go(func() error {
    91  		if err := helpers.ComponentsStarted(ctx, []e2etypes.ComponentRunner{eth1Node, bootNode}); err != nil {
    92  			return fmt.Errorf("beacon nodes require ETH1 and boot node to run: %w", err)
    93  		}
    94  		beaconNodes.SetENR(bootNode.ENR())
    95  		return beaconNodes.Start(ctx)
    96  	})
    97  
    98  	// Validator nodes.
    99  	validatorNodes := components.NewValidatorNodeSet(config)
   100  	g.Go(func() error {
   101  		if err := helpers.ComponentsStarted(ctx, []e2etypes.ComponentRunner{beaconNodes}); err != nil {
   102  			return fmt.Errorf("validator nodes require beacon nodes to run: %w", err)
   103  		}
   104  		return validatorNodes.Start(ctx)
   105  	})
   106  
   107  	// Slasher nodes.
   108  	var slasherNodes e2etypes.ComponentRunner
   109  	if config.TestSlasher {
   110  		slasherNodes := components.NewSlasherNodeSet(config)
   111  		g.Go(func() error {
   112  			if err := helpers.ComponentsStarted(ctx, []e2etypes.ComponentRunner{beaconNodes}); err != nil {
   113  				return fmt.Errorf("slasher nodes require beacon nodes to run: %w", err)
   114  			}
   115  			return slasherNodes.Start(ctx)
   116  		})
   117  	}
   118  
   119  	// Run E2E evaluators and tests.
   120  	g.Go(func() error {
   121  		// When everything is done, cancel parent context (will stop all spawned nodes).
   122  		defer func() {
   123  			log.Info("All E2E evaluations are finished, cleaning up")
   124  			done()
   125  		}()
   126  
   127  		// Wait for all required nodes to start.
   128  		requiredComponents := []e2etypes.ComponentRunner{
   129  			eth1Node, bootNode, beaconNodes, validatorNodes,
   130  		}
   131  		if config.TestSlasher && slasherNodes != nil {
   132  			requiredComponents = append(requiredComponents, slasherNodes)
   133  		}
   134  		ctxAllNodesReady, cancel := context.WithTimeout(ctx, allNodesStartTimeout)
   135  		defer cancel()
   136  		if err := helpers.ComponentsStarted(ctxAllNodesReady, requiredComponents); err != nil {
   137  			return fmt.Errorf("components take too long to start: %w", err)
   138  		}
   139  
   140  		// Since defer unwraps in LIFO order, parent context will be closed only after logs are written.
   141  		defer helpers.LogOutput(t, config)
   142  		if config.UsePprof {
   143  			defer func() {
   144  				log.Info("Writing output pprof files")
   145  				for i := 0; i < e2e.TestParams.BeaconNodeCount; i++ {
   146  					assert.NoError(t, helpers.WritePprofFiles(e2e.TestParams.LogPath, i))
   147  				}
   148  			}()
   149  		}
   150  
   151  		// Blocking, wait period varies depending on number of validators.
   152  		r.waitForChainStart()
   153  
   154  		// Failing early in case chain doesn't start.
   155  		if t.Failed() {
   156  			return errors.New("chain cannot start")
   157  		}
   158  
   159  		if config.TestDeposits {
   160  			log.Info("Running deposit tests")
   161  			r.testDeposits(ctx, g, eth1Node, []e2etypes.ComponentRunner{beaconNodes})
   162  		}
   163  
   164  		// Create GRPC connection to beacon nodes.
   165  		conns, closeConns, err := helpers.NewLocalConnections(ctx, e2e.TestParams.BeaconNodeCount)
   166  		require.NoError(t, err, "Cannot create local connections")
   167  		defer closeConns()
   168  
   169  		// Calculate genesis time.
   170  		nodeClient := eth.NewNodeClient(conns[0])
   171  		genesis, err := nodeClient.GetGenesis(context.Background(), &emptypb.Empty{})
   172  		require.NoError(t, err)
   173  		tickingStartTime := helpers.EpochTickerStartTime(genesis)
   174  
   175  		// Run assigned evaluators.
   176  		if err := r.runEvaluators(conns, tickingStartTime); err != nil {
   177  			return err
   178  		}
   179  
   180  		// If requested, run sync test.
   181  		if !config.TestSync {
   182  			return nil
   183  		}
   184  		return r.testBeaconChainSync(ctx, g, conns, tickingStartTime, bootNode.ENR())
   185  	})
   186  
   187  	if err := g.Wait(); err != nil && !errors.Is(err, context.Canceled) {
   188  		// At the end of the main evaluator goroutine all nodes are killed, no need to fail the test.
   189  		if strings.Contains(err.Error(), "signal: killed") {
   190  			return
   191  		}
   192  		t.Fatalf("E2E test ended in error: %v", err)
   193  	}
   194  }
   195  
   196  // waitForChainStart allows to wait up until beacon nodes are started.
   197  func (r *testRunner) waitForChainStart() {
   198  	// Sleep depending on the count of validators, as generating the genesis state could take some time.
   199  	time.Sleep(time.Duration(params.BeaconConfig().GenesisDelay) * time.Second)
   200  	beaconLogFile, err := os.Open(path.Join(e2e.TestParams.LogPath, fmt.Sprintf(e2e.BeaconNodeLogFileName, 0)))
   201  	require.NoError(r.t, err)
   202  
   203  	r.t.Run("chain started", func(t *testing.T) {
   204  		require.NoError(t, helpers.WaitForTextInFile(beaconLogFile, "Chain started in sync service"), "Chain did not start")
   205  	})
   206  }
   207  
   208  // runEvaluators executes assigned evaluators.
   209  func (r *testRunner) runEvaluators(conns []*grpc.ClientConn, tickingStartTime time.Time) error {
   210  	t, config := r.t, r.config
   211  	secondsPerEpoch := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
   212  	ticker := helpers.NewEpochTicker(tickingStartTime, secondsPerEpoch)
   213  	for currentEpoch := range ticker.C() {
   214  		for _, evaluator := range config.Evaluators {
   215  			// Only run if the policy says so.
   216  			if !evaluator.Policy(types.Epoch(currentEpoch)) {
   217  				continue
   218  			}
   219  			t.Run(fmt.Sprintf(evaluator.Name, currentEpoch), func(t *testing.T) {
   220  				err := evaluator.Evaluation(conns...)
   221  				assert.NoError(t, err, "Evaluation failed for epoch %d: %v", currentEpoch, err)
   222  			})
   223  		}
   224  
   225  		if t.Failed() || currentEpoch >= config.EpochsToRun-1 {
   226  			ticker.Done()
   227  			if t.Failed() {
   228  				return errors.New("test failed")
   229  			}
   230  			break
   231  		}
   232  	}
   233  	return nil
   234  }
   235  
   236  // testDeposits runs tests when config.TestDeposits is enabled.
   237  func (r *testRunner) testDeposits(ctx context.Context, g *errgroup.Group,
   238  	eth1Node *components.Eth1Node, requiredNodes []e2etypes.ComponentRunner) {
   239  	minGenesisActiveCount := int(params.BeaconConfig().MinGenesisActiveValidatorCount)
   240  
   241  	depositCheckValidator := components.NewValidatorNode(r.config, int(e2e.DepositCount), e2e.TestParams.BeaconNodeCount, minGenesisActiveCount)
   242  	g.Go(func() error {
   243  		if err := helpers.ComponentsStarted(ctx, requiredNodes); err != nil {
   244  			return fmt.Errorf("deposit check validator node requires beacon nodes to run: %w", err)
   245  		}
   246  		go func() {
   247  			err := components.SendAndMineDeposits(eth1Node.KeystorePath(), int(e2e.DepositCount), minGenesisActiveCount, false /* partial */)
   248  			if err != nil {
   249  				r.t.Fatal(err)
   250  			}
   251  		}()
   252  		return depositCheckValidator.Start(ctx)
   253  	})
   254  }
   255  
   256  // testBeaconChainSync creates another beacon node, and tests whether it can sync to head using previous nodes.
   257  func (r *testRunner) testBeaconChainSync(ctx context.Context, g *errgroup.Group,
   258  	conns []*grpc.ClientConn, tickingStartTime time.Time, enr string) error {
   259  	t, config := r.t, r.config
   260  	index := e2e.TestParams.BeaconNodeCount
   261  	syncBeaconNode := components.NewBeaconNode(config, index, enr)
   262  	g.Go(func() error {
   263  		return syncBeaconNode.Start(ctx)
   264  	})
   265  	if err := helpers.ComponentsStarted(ctx, []e2etypes.ComponentRunner{syncBeaconNode}); err != nil {
   266  		return fmt.Errorf("sync beacon node not ready: %w", err)
   267  	}
   268  	syncConn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", e2e.TestParams.BeaconNodeRPCPort+index), grpc.WithInsecure())
   269  	require.NoError(t, err, "Failed to dial")
   270  	conns = append(conns, syncConn)
   271  
   272  	// Sleep a second for every 4 blocks that need to be synced for the newly started node.
   273  	secondsPerEpoch := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
   274  	extraSecondsToSync := (config.EpochsToRun)*secondsPerEpoch + uint64(params.BeaconConfig().SlotsPerEpoch.Div(4).Mul(config.EpochsToRun))
   275  	waitForSync := tickingStartTime.Add(time.Duration(extraSecondsToSync) * time.Second)
   276  	time.Sleep(time.Until(waitForSync))
   277  
   278  	syncLogFile, err := os.Open(path.Join(e2e.TestParams.LogPath, fmt.Sprintf(e2e.BeaconNodeLogFileName, index)))
   279  	require.NoError(t, err)
   280  	defer helpers.LogErrorOutput(t, syncLogFile, "beacon chain node", index)
   281  	t.Run("sync completed", func(t *testing.T) {
   282  		assert.NoError(t, helpers.WaitForTextInFile(syncLogFile, "Synced up to"), "Failed to sync")
   283  	})
   284  	if t.Failed() {
   285  		return errors.New("cannot sync beacon node")
   286  	}
   287  
   288  	// Sleep a slot to make sure the synced state is made.
   289  	time.Sleep(time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second)
   290  	syncEvaluators := []e2etypes.Evaluator{ev.FinishedSyncing, ev.AllNodesHaveSameHead}
   291  	for _, evaluator := range syncEvaluators {
   292  		t.Run(evaluator.Name, func(t *testing.T) {
   293  			assert.NoError(t, evaluator.Evaluation(conns...), "Evaluation failed for sync node")
   294  		})
   295  	}
   296  
   297  	return nil
   298  }