|
|
- package p2p
-
- import (
- "context"
- "fmt"
- "sync"
-
- "github.com/gogo/protobuf/proto"
-
- "github.com/tendermint/tendermint/types"
- )
-
- // Envelope contains a message with sender/receiver routing info.
- type Envelope struct {
- From types.NodeID // sender (empty if outbound)
- To types.NodeID // receiver (empty if inbound)
- Broadcast bool // send to all connected peers (ignores To)
- Message proto.Message // message payload
- ChannelID ChannelID
- }
-
- // Wrapper is a Protobuf message that can contain a variety of inner messages
- // (e.g. via oneof fields). If a Channel's message type implements Wrapper, the
- // Router will automatically wrap outbound messages and unwrap inbound messages,
- // such that reactors do not have to do this themselves.
- type Wrapper interface {
- proto.Message
-
- // Wrap will take a message and wrap it in this one if possible.
- Wrap(proto.Message) error
-
- // Unwrap will unwrap the inner message contained in this message.
- Unwrap() (proto.Message, error)
- }
-
- // PeerError is a peer error reported via Channel.Error.
- //
- // FIXME: This currently just disconnects the peer, which is too simplistic.
- // For example, some errors should be logged, some should cause disconnects,
- // and some should ban the peer.
- //
- // FIXME: This should probably be replaced by a more general PeerBehavior
- // concept that can mark good and bad behavior and contributes to peer scoring.
- // It should possibly also allow reactors to request explicit actions, e.g.
- // disconnection or banning, in addition to doing this based on aggregates.
- type PeerError struct {
- NodeID types.NodeID
- Err error
- }
-
- func (pe PeerError) Error() string { return fmt.Sprintf("peer=%q: %s", pe.NodeID, pe.Err.Error()) }
- func (pe PeerError) Unwrap() error { return pe.Err }
-
- // Channel is a bidirectional channel to exchange Protobuf messages with peers.
- // Each message is wrapped in an Envelope to specify its sender and receiver.
- type Channel struct {
- ID ChannelID
- inCh <-chan Envelope // inbound messages (peers to reactors)
- outCh chan<- Envelope // outbound messages (reactors to peers)
- errCh chan<- PeerError // peer error reporting
-
- messageType proto.Message // the channel's message type, used for unmarshaling
- }
-
- // NewChannel creates a new channel. It is primarily for internal and test
- // use, reactors should use Router.OpenChannel().
- func NewChannel(
- id ChannelID,
- messageType proto.Message,
- inCh <-chan Envelope,
- outCh chan<- Envelope,
- errCh chan<- PeerError,
- ) *Channel {
- return &Channel{
- ID: id,
- messageType: messageType,
- inCh: inCh,
- outCh: outCh,
- errCh: errCh,
- }
- }
-
- // Send blocks until the envelope has been sent, or until ctx ends.
- // An error only occurs if the context ends before the send completes.
- func (ch *Channel) Send(ctx context.Context, envelope Envelope) error {
- select {
- case <-ctx.Done():
- return ctx.Err()
- case ch.outCh <- envelope:
- return nil
- }
- }
-
- // SendError blocks until the given error has been sent, or ctx ends.
- // An error only occurs if the context ends before the send completes.
- func (ch *Channel) SendError(ctx context.Context, pe PeerError) error {
- select {
- case <-ctx.Done():
- return ctx.Err()
- case ch.errCh <- pe:
- return nil
- }
- }
-
- // Receive returns a new unbuffered iterator to receive messages from ch.
- // The iterator runs until ctx ends.
- func (ch *Channel) Receive(ctx context.Context) *ChannelIterator {
- iter := &ChannelIterator{
- pipe: make(chan Envelope), // unbuffered
- }
- go func() {
- defer close(iter.pipe)
- iteratorWorker(ctx, ch, iter.pipe)
- }()
- return iter
- }
-
- // ChannelIterator provides a context-aware path for callers
- // (reactors) to process messages from the P2P layer without relying
- // on the implementation details of the P2P layer. Channel provides
- // access to it's Outbound stream as an iterator, and the
- // MergedChannelIterator makes it possible to combine multiple
- // channels into a single iterator.
- type ChannelIterator struct {
- pipe chan Envelope
- current *Envelope
- }
-
- func iteratorWorker(ctx context.Context, ch *Channel, pipe chan Envelope) {
- for {
- select {
- case <-ctx.Done():
- return
- case envelope := <-ch.inCh:
- select {
- case <-ctx.Done():
- return
- case pipe <- envelope:
- }
- }
- }
- }
-
- // Next returns true when the Envelope value has advanced, and false
- // when the context is canceled or iteration should stop. If an iterator has returned false,
- // it will never return true again.
- // in general, use Next, as in:
- //
- // for iter.Next(ctx) {
- // envelope := iter.Envelope()
- // // ... do things ...
- // }
- //
- func (iter *ChannelIterator) Next(ctx context.Context) bool {
- select {
- case <-ctx.Done():
- iter.current = nil
- return false
- case envelope, ok := <-iter.pipe:
- if !ok {
- iter.current = nil
- return false
- }
-
- iter.current = &envelope
-
- return true
- }
- }
-
- // Envelope returns the current Envelope object held by the
- // iterator. When the last call to Next returned true, Envelope will
- // return a non-nil object. If Next returned false then Envelope is
- // always nil.
- func (iter *ChannelIterator) Envelope() *Envelope { return iter.current }
-
- // MergedChannelIterator produces an iterator that merges the
- // messages from the given channels in arbitrary order.
- //
- // This allows the caller to consume messages from multiple channels
- // without needing to manage the concurrency separately.
- func MergedChannelIterator(ctx context.Context, chs ...*Channel) *ChannelIterator {
- iter := &ChannelIterator{
- pipe: make(chan Envelope), // unbuffered
- }
- wg := new(sync.WaitGroup)
-
- for _, ch := range chs {
- wg.Add(1)
- go func(ch *Channel) {
- defer wg.Done()
- iteratorWorker(ctx, ch, iter.pipe)
- }(ch)
- }
-
- done := make(chan struct{})
- go func() { defer close(done); wg.Wait() }()
-
- go func() {
- defer close(iter.pipe)
- // we could return early if the context is canceled,
- // but this is safer because it means the pipe stays
- // open until all of the ch worker threads end, which
- // should happen very quickly.
- <-done
- }()
-
- return iter
- }
|