github.com/prysmaticlabs/prysm@v1.4.4/tools/forkchecker/forkchecker.go (about)

     1  /**
     2   * Fork choice checker
     3   *
     4   * A gRPC client that polls beacon node at every slot to log or compare nodes current head.
     5   *
     6   * Example: 2 beacon nodes with 2 gRPC end points, 127.0.0.1:4000 and 127.0.0.1:4001
     7   * For logging heads: forkchecker --endpoint 127.0.0.1:4000 --endpoint 127.0.0.1:4001
     8   * For comparing heads: forkchecker --endpoint 127.0.0.1:4000 --endpoint 127.0.0.1:4001 --compare
     9   */
    10  package main
    11  
    12  import (
    13  	"context"
    14  	"encoding/hex"
    15  	"flag"
    16  	"reflect"
    17  	"time"
    18  
    19  	types "github.com/prysmaticlabs/eth2-types"
    20  	pb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    21  	"github.com/prysmaticlabs/prysm/shared/params"
    22  	"github.com/sirupsen/logrus"
    23  	"google.golang.org/grpc"
    24  	"google.golang.org/protobuf/types/known/emptypb"
    25  )
    26  
    27  var log = logrus.WithField("prefix", "forkchoice_checker")
    28  
    29  type endpoint []string
    30  
    31  func (e *endpoint) String() string {
    32  	return "gRPC endpoints"
    33  }
    34  
    35  // Set adds endpoint value to list.
    36  func (e *endpoint) Set(value string) error {
    37  	*e = append(*e, value)
    38  	return nil
    39  }
    40  
    41  func main() {
    42  	var endpts endpoint
    43  	clients := make(map[string]pb.BeaconChainClient)
    44  
    45  	flag.Var(&endpts, "endpoint", "Specify gRPC end points for beacon node")
    46  	compare := flag.Bool("compare", false, "Enable head comparisons between all end points")
    47  	flag.Parse()
    48  
    49  	for _, endpt := range endpts {
    50  		conn, err := grpc.Dial(endpt, grpc.WithInsecure())
    51  		if err != nil {
    52  			log.Fatalf("fail to dial: %v", err)
    53  		}
    54  		clients[endpt] = pb.NewBeaconChainClient(conn)
    55  	}
    56  
    57  	ticker := time.NewTicker(time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second)
    58  	go func() {
    59  		for range ticker.C {
    60  			if *compare {
    61  				compareHeads(clients)
    62  			} else {
    63  				displayHeads(clients)
    64  			}
    65  		}
    66  	}()
    67  	select {}
    68  }
    69  
    70  // log heads for all RPC end points
    71  func displayHeads(clients map[string]pb.BeaconChainClient) {
    72  	for endpt, client := range clients {
    73  		head, err := client.GetChainHead(context.Background(), &emptypb.Empty{})
    74  		if err != nil {
    75  			log.Fatal(err)
    76  		}
    77  		logHead(endpt, head)
    78  	}
    79  }
    80  
    81  // compare heads between all RPC end points, log the missmatch if there's one.
    82  func compareHeads(clients map[string]pb.BeaconChainClient) {
    83  	endpt1 := randomEndpt(clients)
    84  	head1, err := clients[endpt1].GetChainHead(context.Background(), &emptypb.Empty{})
    85  	if err != nil {
    86  		log.Fatal(err)
    87  	}
    88  
    89  	log.Infof("Comparing all heads for head slot :%d", head1.HeadSlot)
    90  	if (head1.HeadSlot+1)%params.BeaconConfig().SlotsPerEpoch == 0 {
    91  		p, err := clients[endpt1].GetValidatorParticipation(context.Background(), &pb.GetValidatorParticipationRequest{})
    92  		if err != nil {
    93  			log.Fatal(err)
    94  		}
    95  		logParticipation(endpt1, p.Participation)
    96  	}
    97  
    98  	for endpt2, client := range clients {
    99  		head2, err := client.GetChainHead(context.Background(), &emptypb.Empty{})
   100  		if err != nil {
   101  			log.Fatal(err)
   102  		}
   103  		if !reflect.DeepEqual(head1, head2) {
   104  			log.Error("Uh oh! Heads missmatched!")
   105  			logHead(endpt1, head1)
   106  			logHead(endpt2, head2)
   107  
   108  			if (head1.HeadSlot+1)%params.BeaconConfig().SlotsPerEpoch == 0 {
   109  				p, err := clients[endpt2].GetValidatorParticipation(context.Background(), &pb.GetValidatorParticipationRequest{
   110  					QueryFilter: &pb.GetValidatorParticipationRequest_Epoch{
   111  						Epoch: types.Epoch(head2.HeadSlot / params.BeaconConfig().SlotsPerEpoch),
   112  					},
   113  				})
   114  				if err != nil {
   115  					log.Fatal(err)
   116  				}
   117  				logParticipation(endpt2, p.Participation)
   118  			}
   119  		}
   120  	}
   121  }
   122  
   123  func logHead(endpt string, head *pb.ChainHead) {
   124  	log.WithFields(
   125  		logrus.Fields{
   126  			"HeadSlot":       head.HeadSlot,
   127  			"HeadRoot":       hex.EncodeToString(head.HeadBlockRoot),
   128  			"JustifiedEpoch": head.JustifiedEpoch,
   129  			"JustifiedRoot":  hex.EncodeToString(head.JustifiedBlockRoot),
   130  			"FinalizedEpoch": head.FinalizedEpoch,
   131  			"FinalizedRoot":  hex.EncodeToString(head.FinalizedBlockRoot),
   132  		}).Info("Head from beacon node ", endpt)
   133  }
   134  
   135  func logParticipation(endpt string, p *pb.ValidatorParticipation) {
   136  	log.WithFields(
   137  		logrus.Fields{
   138  			"VotedEther":        p.VotedEther,
   139  			"TotalEther":        p.EligibleEther,
   140  			"ParticipationRate": p.GlobalParticipationRate,
   141  		}).Info("Participation rate from beacon node ", endpt)
   142  }
   143  
   144  func randomEndpt(clients map[string]pb.BeaconChainClient) string {
   145  	for endpt := range clients {
   146  		return endpt
   147  	}
   148  	return ""
   149  }