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 }