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 }