bitbucket.org/number571/tendermint@v0.8.14/internal/p2p/shim.go (about)

     1  package p2p
     2  
     3  import (
     4  	"errors"
     5  	"sort"
     6  
     7  	"bitbucket.org/number571/tendermint/libs/log"
     8  	"github.com/gogo/protobuf/proto"
     9  )
    10  
    11  // ============================================================================
    12  // TODO: Types and business logic below are temporary and will be removed once
    13  // the legacy p2p stack is removed in favor of the new model.
    14  //
    15  // ref: https://bitbucket.org/number571/tendermint/issues/5670
    16  // ============================================================================
    17  
    18  var _ Reactor = (*ReactorShim)(nil)
    19  
    20  type (
    21  	messageValidator interface {
    22  		Validate() error
    23  	}
    24  
    25  	// ReactorShim defines a generic shim wrapper around a BaseReactor. It is
    26  	// responsible for wiring up legacy p2p behavior to the new p2p semantics
    27  	// (e.g. proxying Envelope messages to legacy peers).
    28  	ReactorShim struct {
    29  		BaseReactor
    30  
    31  		Name        string
    32  		PeerUpdates *PeerUpdates
    33  		Channels    map[ChannelID]*ChannelShim
    34  	}
    35  
    36  	// ChannelShim defines a generic shim wrapper around a legacy p2p channel
    37  	// and the new p2p Channel. It also includes the raw bi-directional Go channels
    38  	// so we can proxy message delivery.
    39  	ChannelShim struct {
    40  		Descriptor *ChannelDescriptor
    41  		Channel    *Channel
    42  		inCh       chan<- Envelope
    43  		outCh      <-chan Envelope
    44  		errCh      <-chan PeerError
    45  	}
    46  
    47  	// ChannelDescriptorShim defines a shim wrapper around a legacy p2p channel
    48  	// and the proto.Message the new p2p Channel is responsible for handling.
    49  	// A ChannelDescriptorShim is not contained in ReactorShim, but is rather
    50  	// used to construct a ReactorShim.
    51  	ChannelDescriptorShim struct {
    52  		MsgType    proto.Message
    53  		Descriptor *ChannelDescriptor
    54  	}
    55  )
    56  
    57  func NewReactorShim(logger log.Logger, name string, descriptors map[ChannelID]*ChannelDescriptorShim) *ReactorShim {
    58  	channels := make(map[ChannelID]*ChannelShim)
    59  
    60  	for _, cds := range descriptors {
    61  		chShim := NewChannelShim(cds, 0)
    62  		channels[chShim.Channel.ID] = chShim
    63  	}
    64  
    65  	rs := &ReactorShim{
    66  		Name:        name,
    67  		PeerUpdates: NewPeerUpdates(make(chan PeerUpdate), 0),
    68  		Channels:    channels,
    69  	}
    70  
    71  	rs.BaseReactor = *NewBaseReactor(name, rs)
    72  	rs.SetLogger(logger)
    73  
    74  	return rs
    75  }
    76  
    77  func NewChannelShim(cds *ChannelDescriptorShim, buf uint) *ChannelShim {
    78  	inCh := make(chan Envelope, buf)
    79  	outCh := make(chan Envelope, buf)
    80  	errCh := make(chan PeerError, buf)
    81  	return &ChannelShim{
    82  		Descriptor: cds.Descriptor,
    83  		Channel: NewChannel(
    84  			ChannelID(cds.Descriptor.ID),
    85  			cds.MsgType,
    86  			inCh,
    87  			outCh,
    88  			errCh,
    89  		),
    90  		inCh:  inCh,
    91  		outCh: outCh,
    92  		errCh: errCh,
    93  	}
    94  }
    95  
    96  // proxyPeerEnvelopes iterates over each p2p Channel and starts a separate
    97  // go-routine where we listen for outbound envelopes sent during Receive
    98  // executions (or anything else that may send on the Channel) and proxy them to
    99  // the corresponding Peer using the To field from the envelope.
   100  func (rs *ReactorShim) proxyPeerEnvelopes() {
   101  	for _, cs := range rs.Channels {
   102  		go func(cs *ChannelShim) {
   103  			for e := range cs.outCh {
   104  				msg := proto.Clone(cs.Channel.messageType)
   105  				msg.Reset()
   106  
   107  				wrapper, ok := msg.(Wrapper)
   108  				if ok {
   109  					if err := wrapper.Wrap(e.Message); err != nil {
   110  						rs.Logger.Error(
   111  							"failed to proxy envelope; failed to wrap message",
   112  							"ch_id", cs.Descriptor.ID,
   113  							"err", err,
   114  						)
   115  						continue
   116  					}
   117  				} else {
   118  					msg = e.Message
   119  				}
   120  
   121  				bz, err := proto.Marshal(msg)
   122  				if err != nil {
   123  					rs.Logger.Error(
   124  						"failed to proxy envelope; failed to encode message",
   125  						"ch_id", cs.Descriptor.ID,
   126  						"err", err,
   127  					)
   128  					continue
   129  				}
   130  
   131  				switch {
   132  				case e.Broadcast:
   133  					rs.Switch.Broadcast(cs.Descriptor.ID, bz)
   134  
   135  				case e.To != "":
   136  					src := rs.Switch.peers.Get(e.To)
   137  					if src == nil {
   138  						rs.Logger.Debug(
   139  							"failed to proxy envelope; failed to find peer",
   140  							"ch_id", cs.Descriptor.ID,
   141  							"peer", e.To,
   142  						)
   143  						continue
   144  					}
   145  
   146  					if !src.Send(cs.Descriptor.ID, bz) {
   147  						// This usually happens when we try to send across a channel
   148  						// that the peer doesn't have open. To avoid bloating the
   149  						// logs we set this to be Debug
   150  						rs.Logger.Debug(
   151  							"failed to proxy message to peer",
   152  							"ch_id", cs.Descriptor.ID,
   153  							"peer", e.To,
   154  						)
   155  					}
   156  
   157  				default:
   158  					rs.Logger.Error("failed to proxy envelope; missing peer ID", "ch_id", cs.Descriptor.ID)
   159  				}
   160  			}
   161  		}(cs)
   162  	}
   163  }
   164  
   165  // handlePeerErrors iterates over each p2p Channel and starts a separate go-routine
   166  // where we listen for peer errors. For each peer error, we find the peer from
   167  // the legacy p2p Switch and execute a StopPeerForError call with the corresponding
   168  // peer error.
   169  func (rs *ReactorShim) handlePeerErrors() {
   170  	for _, cs := range rs.Channels {
   171  		go func(cs *ChannelShim) {
   172  			for pErr := range cs.errCh {
   173  				if pErr.NodeID != "" {
   174  					peer := rs.Switch.peers.Get(pErr.NodeID)
   175  					if peer == nil {
   176  						rs.Logger.Error("failed to handle peer error; failed to find peer", "peer", pErr.NodeID)
   177  						continue
   178  					}
   179  
   180  					rs.Switch.StopPeerForError(peer, pErr.Err)
   181  				}
   182  			}
   183  		}(cs)
   184  	}
   185  }
   186  
   187  // OnStart executes the reactor shim's OnStart hook where we start all the
   188  // necessary go-routines in order to proxy peer envelopes and errors per p2p
   189  // Channel.
   190  func (rs *ReactorShim) OnStart() error {
   191  	if rs.Switch == nil {
   192  		return errors.New("proxyPeerEnvelopes: reactor shim switch is nil")
   193  	}
   194  
   195  	// start envelope proxying and peer error handling in separate go routines
   196  	rs.proxyPeerEnvelopes()
   197  	rs.handlePeerErrors()
   198  
   199  	return nil
   200  }
   201  
   202  // GetChannel returns a p2p Channel reference for a given ChannelID. If no
   203  // Channel exists, nil is returned.
   204  func (rs *ReactorShim) GetChannel(cID ChannelID) *Channel {
   205  	channelShim, ok := rs.Channels[cID]
   206  	if ok {
   207  		return channelShim.Channel
   208  	}
   209  
   210  	return nil
   211  }
   212  
   213  // GetChannels implements the legacy Reactor interface for getting a slice of all
   214  // the supported ChannelDescriptors.
   215  func (rs *ReactorShim) GetChannels() []*ChannelDescriptor {
   216  	sortedChIDs := make([]ChannelID, 0, len(rs.Channels))
   217  	for cID := range rs.Channels {
   218  		sortedChIDs = append(sortedChIDs, cID)
   219  	}
   220  
   221  	sort.Slice(sortedChIDs, func(i, j int) bool { return sortedChIDs[i] < sortedChIDs[j] })
   222  
   223  	descriptors := make([]*ChannelDescriptor, len(rs.Channels))
   224  	for i, cID := range sortedChIDs {
   225  		descriptors[i] = rs.Channels[cID].Descriptor
   226  	}
   227  
   228  	return descriptors
   229  }
   230  
   231  // AddPeer sends a PeerUpdate with status PeerStatusUp on the PeerUpdateCh.
   232  // The embedding reactor must be sure to listen for messages on this channel to
   233  // handle adding a peer.
   234  func (rs *ReactorShim) AddPeer(peer Peer) {
   235  	select {
   236  	case rs.PeerUpdates.reactorUpdatesCh <- PeerUpdate{NodeID: peer.ID(), Status: PeerStatusUp}:
   237  		rs.Logger.Debug("sent peer update", "reactor", rs.Name, "peer", peer.ID(), "status", PeerStatusUp)
   238  
   239  	case <-rs.PeerUpdates.Done():
   240  		// NOTE: We explicitly DO NOT close the PeerUpdatesCh's updateCh go channel.
   241  		// This is because there may be numerous spawned goroutines that are
   242  		// attempting to send on the updateCh go channel and when the reactor stops
   243  		// we do not want to preemptively close the channel as that could result in
   244  		// panics sending on a closed channel. This also means that reactors MUST
   245  		// be certain there are NO listeners on the updateCh channel when closing or
   246  		// stopping.
   247  	}
   248  }
   249  
   250  // RemovePeer sends a PeerUpdate with status PeerStatusDown on the PeerUpdateCh.
   251  // The embedding reactor must be sure to listen for messages on this channel to
   252  // handle removing a peer.
   253  func (rs *ReactorShim) RemovePeer(peer Peer, reason interface{}) {
   254  	select {
   255  	case rs.PeerUpdates.reactorUpdatesCh <- PeerUpdate{NodeID: peer.ID(), Status: PeerStatusDown}:
   256  		rs.Logger.Debug(
   257  			"sent peer update",
   258  			"reactor", rs.Name,
   259  			"peer", peer.ID(),
   260  			"reason", reason,
   261  			"status", PeerStatusDown,
   262  		)
   263  
   264  	case <-rs.PeerUpdates.Done():
   265  		// NOTE: We explicitly DO NOT close the PeerUpdatesCh's updateCh go channel.
   266  		// This is because there may be numerous spawned goroutines that are
   267  		// attempting to send on the updateCh go channel and when the reactor stops
   268  		// we do not want to preemptively close the channel as that could result in
   269  		// panics sending on a closed channel. This also means that reactors MUST
   270  		// be certain there are NO listeners on the updateCh channel when closing or
   271  		// stopping.
   272  	}
   273  }
   274  
   275  // Receive implements a generic wrapper around implementing the Receive method
   276  // on the legacy Reactor p2p interface. If the reactor is running, Receive will
   277  // find the corresponding new p2p Channel, create and decode the appropriate
   278  // proto.Message from the msgBytes, execute any validation and finally construct
   279  // and send a p2p Envelope on the appropriate p2p Channel.
   280  func (rs *ReactorShim) Receive(chID byte, src Peer, msgBytes []byte) {
   281  	if !rs.IsRunning() {
   282  		return
   283  	}
   284  
   285  	cID := ChannelID(chID)
   286  	channelShim, ok := rs.Channels[cID]
   287  	if !ok {
   288  		rs.Logger.Error("unexpected channel", "peer", src, "ch_id", chID)
   289  		return
   290  	}
   291  
   292  	msg := proto.Clone(channelShim.Channel.messageType)
   293  	msg.Reset()
   294  
   295  	if err := proto.Unmarshal(msgBytes, msg); err != nil {
   296  		rs.Logger.Error("error decoding message", "peer", src, "ch_id", cID, "err", err)
   297  		rs.Switch.StopPeerForError(src, err)
   298  		return
   299  	}
   300  
   301  	validator, ok := msg.(messageValidator)
   302  	if ok {
   303  		if err := validator.Validate(); err != nil {
   304  			rs.Logger.Error("invalid message", "peer", src, "ch_id", cID, "err", err)
   305  			rs.Switch.StopPeerForError(src, err)
   306  			return
   307  		}
   308  	}
   309  
   310  	wrapper, ok := msg.(Wrapper)
   311  	if ok {
   312  		var err error
   313  
   314  		msg, err = wrapper.Unwrap()
   315  		if err != nil {
   316  			rs.Logger.Error("failed to unwrap message", "peer", src, "ch_id", chID, "err", err)
   317  			return
   318  		}
   319  	}
   320  
   321  	select {
   322  	case channelShim.inCh <- Envelope{From: src.ID(), Message: msg}:
   323  		rs.Logger.Debug("proxied envelope", "reactor", rs.Name, "ch_id", cID, "peer", src.ID())
   324  
   325  	case <-channelShim.Channel.Done():
   326  		// NOTE: We explicitly DO NOT close the p2p Channel's inbound go channel.
   327  		// This is because there may be numerous spawned goroutines that are
   328  		// attempting to send on the inbound channel and when the reactor stops we
   329  		// do not want to preemptively close the channel as that could result in
   330  		// panics sending on a closed channel. This also means that reactors MUST
   331  		// be certain there are NO listeners on the inbound channel when closing or
   332  		// stopping.
   333  	}
   334  }