github.com/prysmaticlabs/prysm@v1.4.4/validator/client/runner.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/pkg/errors"
    10  	types "github.com/prysmaticlabs/eth2-types"
    11  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    12  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    13  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    14  	"github.com/prysmaticlabs/prysm/shared/params"
    15  	"github.com/prysmaticlabs/prysm/validator/client/iface"
    16  	"github.com/prysmaticlabs/prysm/validator/keymanager/remote"
    17  	"go.opencensus.io/trace"
    18  	"google.golang.org/grpc/codes"
    19  	"google.golang.org/grpc/status"
    20  )
    21  
    22  // time to wait before trying to reconnect with beacon node.
    23  var backOffPeriod = 10 * time.Second
    24  
    25  // Run the main validator routine. This routine exits if the context is
    26  // canceled.
    27  //
    28  // Order of operations:
    29  // 1 - Initialize validator data
    30  // 2 - Wait for validator activation
    31  // 3 - Wait for the next slot start
    32  // 4 - Update assignments
    33  // 5 - Determine role at current slot
    34  // 6 - Perform assigned role, if any
    35  func run(ctx context.Context, v iface.Validator) {
    36  	cleanup := v.Done
    37  	defer cleanup()
    38  	if err := v.WaitForWalletInitialization(ctx); err != nil {
    39  		// log.Fatalf will prevent defer from being called
    40  		cleanup()
    41  		log.Fatalf("Wallet is not ready: %v", err)
    42  	}
    43  	if featureconfig.Get().SlasherProtection {
    44  		if err := v.SlasherReady(ctx); err != nil {
    45  			log.Fatalf("Slasher is not ready: %v", err)
    46  		}
    47  	}
    48  	ticker := time.NewTicker(backOffPeriod)
    49  	defer ticker.Stop()
    50  
    51  	var headSlot types.Slot
    52  	firstTime := true
    53  	for {
    54  		if !firstTime {
    55  			if ctx.Err() != nil {
    56  				log.Info("Context canceled, stopping validator")
    57  				return // Exit if context is canceled.
    58  			}
    59  			<-ticker.C
    60  		} else {
    61  			firstTime = false
    62  		}
    63  		err := v.WaitForChainStart(ctx)
    64  		if isConnectionError(err) {
    65  			log.Warnf("Could not determine if beacon chain started: %v", err)
    66  			continue
    67  		}
    68  		if err != nil {
    69  			log.Fatalf("Could not determine if beacon chain started: %v", err)
    70  		}
    71  		err = v.WaitForSync(ctx)
    72  		if isConnectionError(err) {
    73  			log.Warnf("Could not determine if beacon chain started: %v", err)
    74  			continue
    75  		}
    76  		if err != nil {
    77  			log.Fatalf("Could not determine if beacon node synced: %v", err)
    78  		}
    79  		err = v.WaitForActivation(ctx, nil /* accountsChangedChan */)
    80  		if isConnectionError(err) {
    81  			log.Warnf("Could not wait for validator activation: %v", err)
    82  			continue
    83  		}
    84  		if err != nil {
    85  			log.Fatalf("Could not wait for validator activation: %v", err)
    86  		}
    87  		err = v.CheckDoppelGanger(ctx)
    88  		if isConnectionError(err) {
    89  			log.Warnf("Could not wait for checking doppelganger: %v", err)
    90  			continue
    91  		}
    92  		if err != nil {
    93  			log.Fatalf("Could not succeed with doppelganger check: %v", err)
    94  		}
    95  		headSlot, err = v.CanonicalHeadSlot(ctx)
    96  		if isConnectionError(err) {
    97  			log.Warnf("Could not get current canonical head slot: %v", err)
    98  			continue
    99  		}
   100  		if err != nil {
   101  			log.Fatalf("Could not get current canonical head slot: %v", err)
   102  		}
   103  		break
   104  	}
   105  
   106  	connectionErrorChannel := make(chan error, 1)
   107  	go v.ReceiveBlocks(ctx, connectionErrorChannel)
   108  	if err := v.UpdateDuties(ctx, headSlot); err != nil {
   109  		handleAssignmentError(err, headSlot)
   110  	}
   111  
   112  	accountsChangedChan := make(chan [][48]byte, 1)
   113  	sub := v.GetKeymanager().SubscribeAccountChanges(accountsChangedChan)
   114  	for {
   115  		slotCtx, cancel := context.WithCancel(ctx)
   116  		ctx, span := trace.StartSpan(ctx, "validator.processSlot")
   117  
   118  		select {
   119  		case <-ctx.Done():
   120  			log.Info("Context canceled, stopping validator")
   121  			span.End()
   122  			cancel()
   123  			sub.Unsubscribe()
   124  			close(accountsChangedChan)
   125  			return // Exit if context is canceled.
   126  		case blocksError := <-connectionErrorChannel:
   127  			if blocksError != nil {
   128  				log.WithError(blocksError).Warn("block stream interrupted")
   129  				go v.ReceiveBlocks(ctx, connectionErrorChannel)
   130  				continue
   131  			}
   132  		case newKeys := <-accountsChangedChan:
   133  			anyActive, err := v.HandleKeyReload(ctx, newKeys)
   134  			if err != nil {
   135  				log.WithError(err).Error("Could not properly handle reloaded keys")
   136  			}
   137  			if !anyActive {
   138  				log.Info("No active keys found. Waiting for activation...")
   139  				err := v.WaitForActivation(ctx, accountsChangedChan)
   140  				if err != nil {
   141  					log.Fatalf("Could not wait for validator activation: %v", err)
   142  				}
   143  			}
   144  		case slot := <-v.NextSlot():
   145  			span.AddAttributes(trace.Int64Attribute("slot", int64(slot)))
   146  
   147  			remoteKm, ok := v.GetKeymanager().(remote.RemoteKeymanager)
   148  			if ok {
   149  				_, err := remoteKm.ReloadPublicKeys(ctx)
   150  				if err != nil {
   151  					log.WithError(err).Error(msgCouldNotFetchKeys)
   152  				}
   153  			}
   154  
   155  			allExited, err := v.AllValidatorsAreExited(ctx)
   156  			if err != nil {
   157  				log.WithError(err).Error("Could not check if validators are exited")
   158  			}
   159  			if allExited {
   160  				log.Info("All validators are exited, no more work to perform...")
   161  				continue
   162  			}
   163  
   164  			deadline := v.SlotDeadline(slot)
   165  			slotCtx, cancel = context.WithDeadline(ctx, deadline)
   166  			log := log.WithField("slot", slot)
   167  			log.WithField("deadline", deadline).Debug("Set deadline for proposals and attestations")
   168  
   169  			// Keep trying to update assignments if they are nil or if we are past an
   170  			// epoch transition in the beacon node's state.
   171  			if err := v.UpdateDuties(ctx, slot); err != nil {
   172  				handleAssignmentError(err, slot)
   173  				cancel()
   174  				span.End()
   175  				continue
   176  			}
   177  
   178  			// Start fetching domain data for the next epoch.
   179  			if helpers.IsEpochEnd(slot) {
   180  				go v.UpdateDomainDataCaches(ctx, slot+1)
   181  			}
   182  
   183  			var wg sync.WaitGroup
   184  
   185  			allRoles, err := v.RolesAt(ctx, slot)
   186  			if err != nil {
   187  				log.WithError(err).Error("Could not get validator roles")
   188  				span.End()
   189  				continue
   190  			}
   191  			for pubKey, roles := range allRoles {
   192  				wg.Add(len(roles))
   193  				for _, role := range roles {
   194  					go func(role iface.ValidatorRole, pubKey [48]byte) {
   195  						defer wg.Done()
   196  						switch role {
   197  						case iface.RoleAttester:
   198  							v.SubmitAttestation(slotCtx, slot, pubKey)
   199  						case iface.RoleProposer:
   200  							v.ProposeBlock(slotCtx, slot, pubKey)
   201  						case iface.RoleAggregator:
   202  							v.SubmitAggregateAndProof(slotCtx, slot, pubKey)
   203  						case iface.RoleUnknown:
   204  							log.WithField("pubKey", fmt.Sprintf("%#x", bytesutil.Trunc(pubKey[:]))).Trace("No active roles, doing nothing")
   205  						default:
   206  							log.Warnf("Unhandled role %v", role)
   207  						}
   208  					}(role, pubKey)
   209  				}
   210  			}
   211  			// Wait for all processes to complete, then report span complete.
   212  
   213  			go func() {
   214  				wg.Wait()
   215  				// Log this client performance in the previous epoch
   216  				v.LogAttestationsSubmitted()
   217  				if err := v.LogValidatorGainsAndLosses(slotCtx, slot); err != nil {
   218  					log.WithError(err).Error("Could not report validator's rewards/penalties")
   219  				}
   220  				if err := v.LogNextDutyTimeLeft(slot); err != nil {
   221  					log.WithError(err).Error("Could not report next count down")
   222  				}
   223  				span.End()
   224  			}()
   225  		}
   226  	}
   227  }
   228  
   229  func isConnectionError(err error) bool {
   230  	return err != nil && errors.Is(err, iface.ErrConnectionIssue)
   231  }
   232  
   233  func handleAssignmentError(err error, slot types.Slot) {
   234  	if errCode, ok := status.FromError(err); ok && errCode.Code() == codes.NotFound {
   235  		log.WithField(
   236  			"epoch", slot/params.BeaconConfig().SlotsPerEpoch,
   237  		).Warn("Validator not yet assigned to epoch")
   238  	} else {
   239  		log.WithField("error", err).Error("Failed to update assignments")
   240  	}
   241  }