github.com/amazechain/amc@v0.1.3/internal/sync/rpc_goodbye.go (about)

     1  package sync
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	ssztype "github.com/amazechain/amc/common/types/ssz"
     7  	"github.com/amazechain/amc/internal/p2p"
     8  	p2ptypes "github.com/amazechain/amc/internal/p2p/types"
     9  	"github.com/amazechain/amc/log"
    10  	"github.com/amazechain/amc/utils"
    11  	"time"
    12  
    13  	libp2pcore "github.com/libp2p/go-libp2p/core"
    14  	"github.com/libp2p/go-libp2p/core/network"
    15  	"github.com/libp2p/go-libp2p/core/peer"
    16  )
    17  
    18  var backOffTime = map[ssztype.SSZUint64]time.Duration{
    19  	// Do not dial peers which are from a different/unverifiable
    20  	// network.
    21  	p2ptypes.GoodbyeCodeWrongNetwork:          24 * time.Hour,
    22  	p2ptypes.GoodbyeCodeUnableToVerifyNetwork: 24 * time.Hour,
    23  	// If local peer is banned, we back off for
    24  	// 2 hours to let the remote peer score us
    25  	// back up again.
    26  	p2ptypes.GoodbyeCodeBadScore:       2 * time.Hour,
    27  	p2ptypes.GoodbyeCodeBanned:         1 * time.Hour,
    28  	p2ptypes.GoodbyeCodeClientShutdown: 1 * time.Hour,
    29  	// Wait 5 minutes before dialing a peer who is
    30  	// 'full'
    31  	p2ptypes.GoodbyeCodeTooManyPeers: 5 * time.Minute,
    32  	p2ptypes.GoodbyeCodeGenericError: 2 * time.Minute,
    33  }
    34  
    35  // goodbyeRPCHandler reads the incoming goodbye rpc message from the peer.
    36  func (s *Service) goodbyeRPCHandler(_ context.Context, msg interface{}, stream libp2pcore.Stream) error {
    37  	SetRPCStreamDeadlines(stream)
    38  
    39  	m, ok := msg.(*ssztype.SSZUint64)
    40  	if !ok {
    41  		return fmt.Errorf("wrong message type for goodbye, got %T, wanted *uint64", msg)
    42  	}
    43  	if err := s.rateLimiter.validateRequest(stream, 1); err != nil {
    44  		return err
    45  	}
    46  	s.rateLimiter.add(stream, 1)
    47  	log.Info("Peer has sent a goodbye message", "peer", stream.Conn().RemotePeer(), "Reason", goodbyeMessage(*m))
    48  	s.cfg.p2p.Peers().SetNextValidTime(stream.Conn().RemotePeer(), goodByeBackoff(*m))
    49  	// closes all streams with the peer
    50  	return s.cfg.p2p.Disconnect(stream.Conn().RemotePeer())
    51  }
    52  
    53  // disconnectBadPeer checks whether peer is considered bad by some scorer, and tries to disconnect
    54  // the peer, if that is the case. Additionally, disconnection reason is obtained from scorer.
    55  func (s *Service) disconnectBadPeer(ctx context.Context, id peer.ID) {
    56  	if !s.cfg.p2p.Peers().IsBad(id) {
    57  		return
    58  	}
    59  	err := s.cfg.p2p.Peers().Scorers().ValidationError(id)
    60  	goodbyeCode := p2ptypes.ErrToGoodbyeCode(err)
    61  	if err == nil {
    62  		goodbyeCode = p2ptypes.GoodbyeCodeBanned
    63  	}
    64  	if err := s.sendGoodByeAndDisconnect(ctx, goodbyeCode, id); err != nil {
    65  		log.Debug("Error when disconnecting with bad peer", "err", err)
    66  	}
    67  }
    68  
    69  // A custom goodbye method that is used by our connection handler, in the
    70  // event we receive bad peers.
    71  func (s *Service) sendGoodbye(ctx context.Context, id peer.ID) error {
    72  	return s.sendGoodByeAndDisconnect(ctx, p2ptypes.GoodbyeCodeGenericError, id)
    73  }
    74  
    75  func (s *Service) sendGoodByeAndDisconnect(ctx context.Context, code p2ptypes.RPCGoodbyeCode, id peer.ID) error {
    76  	lock := utils.NewMultilock(id.String())
    77  	lock.Lock()
    78  	defer lock.Unlock()
    79  	// In the event we are already disconnected, exit early from the
    80  	// goodbye method to prevent redundant streams from being created.
    81  	if s.cfg.p2p.Host().Network().Connectedness(id) == network.NotConnected {
    82  		return nil
    83  	}
    84  	if err := s.sendGoodByeMessage(ctx, code, id); err != nil {
    85  		log.Debug("Could not send goodbye message to peer", "peer", id, "error", err)
    86  	}
    87  	return s.cfg.p2p.Disconnect(id)
    88  }
    89  
    90  func (s *Service) sendGoodByeMessage(ctx context.Context, code p2ptypes.RPCGoodbyeCode, id peer.ID) error {
    91  	ctx, cancel := context.WithTimeout(ctx, respTimeout)
    92  	defer cancel()
    93  
    94  	topic, err := p2p.TopicFromMessage(p2p.GoodbyeMessageName)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	stream, err := s.cfg.p2p.Send(ctx, &code, topic, id)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	defer closeStream(stream)
   103  
   104  	log.Debug("Sending Goodbye message to peer", "Reason", goodbyeMessage(code), "peer", stream.Conn().RemotePeer())
   105  
   106  	// Wait up to the response timeout for the peer to receive the goodbye
   107  	// and close the stream (or disconnect). We usually don't bother waiting
   108  	// around for an EOF, but we're going to close this connection
   109  	// immediately after we say goodbye.
   110  	//
   111  	// NOTE: we don't actually check the response as there's nothing we can
   112  	// do if something fails. We just need to wait for it.
   113  	SetStreamReadDeadline(stream, respTimeout)
   114  	_, _err := stream.Read([]byte{0})
   115  	_ = _err
   116  
   117  	return nil
   118  }
   119  
   120  func goodbyeMessage(num p2ptypes.RPCGoodbyeCode) string {
   121  	reason, ok := p2ptypes.GoodbyeCodeMessages[num]
   122  	if ok {
   123  		return reason
   124  	}
   125  	return fmt.Sprintf("unknown goodbye value of %d received", num)
   126  }
   127  
   128  // determines which backoff time to use depending on the
   129  // goodbye code provided.
   130  func goodByeBackoff(num p2ptypes.RPCGoodbyeCode) time.Time {
   131  	duration, ok := backOffTime[num]
   132  	if !ok {
   133  		return time.Time{}
   134  	}
   135  	return time.Now().Add(duration)
   136  }