github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/integration/network_test.go (about)

     1  package integration_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/hashicorp/go-multierror"
     9  
    10  	"github.com/onflow/flow-go/model/flow"
    11  	"github.com/onflow/flow-go/network"
    12  	"github.com/onflow/flow-go/network/channels"
    13  	"github.com/onflow/flow-go/network/mocknetwork"
    14  )
    15  
    16  // TODO replace this type with `network/stub/hub.go`
    17  // Hub is a test helper that mocks a network overlay.
    18  // It maintains a set of network instances and enables them to directly exchange message
    19  // over the memory.
    20  type Hub struct {
    21  	networks   map[flow.Identifier]*Network
    22  	filter     BlockOrDelayFunc
    23  	identities flow.IdentityList
    24  }
    25  
    26  // NewNetworkHub creates and returns a new Hub instance.
    27  func NewNetworkHub() *Hub {
    28  	return &Hub{
    29  		networks:   make(map[flow.Identifier]*Network),
    30  		identities: flow.IdentityList{},
    31  	}
    32  }
    33  
    34  // WithFilter is an option method that sets filter of the Hub instance.
    35  func (h *Hub) WithFilter(filter BlockOrDelayFunc) *Hub {
    36  	h.filter = filter
    37  	return h
    38  }
    39  
    40  // AddNetwork stores the reference of the Network in the Hub, in order for networks to find
    41  // other networks to send events directly.
    42  func (h *Hub) AddNetwork(originID flow.Identifier, node *Node) *Network {
    43  	net := &Network{
    44  		ctx:      context.Background(),
    45  		hub:      h,
    46  		originID: originID,
    47  		conduits: make(map[channels.Channel]*Conduit),
    48  		node:     node,
    49  	}
    50  	h.networks[originID] = net
    51  	h.identities = append(h.identities, node.id)
    52  	return net
    53  }
    54  
    55  // TODO replace this type with `network/stub/network.go`
    56  // Network is a mocked Network layer made for testing engine's behavior.
    57  // It represents the Network layer of a single node. A node can attach several engines of
    58  // itself to the Network, and hence enabling them send and receive message.
    59  // When an engine is attached on a Network instance, the mocked Network delivers
    60  // all engine's events to others using an in-memory delivery mechanism.
    61  type Network struct {
    62  	ctx      context.Context
    63  	hub      *Hub
    64  	node     *Node
    65  	originID flow.Identifier
    66  	conduits map[channels.Channel]*Conduit
    67  	mocknetwork.Network
    68  }
    69  
    70  var _ network.EngineRegistry = (*Network)(nil)
    71  
    72  // Register registers an Engine of the attached node to the channel via a Conduit, and returns the
    73  // Conduit instance.
    74  func (n *Network) Register(channel channels.Channel, engine network.MessageProcessor) (network.Conduit, error) {
    75  	ctx, cancel := context.WithCancel(n.ctx)
    76  	con := &Conduit{
    77  		ctx:     ctx,
    78  		cancel:  cancel,
    79  		net:     n,
    80  		channel: channel,
    81  		queue:   make(chan message, 1024),
    82  	}
    83  
    84  	go func() {
    85  		for msg := range con.queue {
    86  			go func(m message) {
    87  				_ = engine.Process(channel, m.originID, m.event)
    88  			}(msg)
    89  		}
    90  	}()
    91  
    92  	n.conduits[channel] = con
    93  	return con, nil
    94  }
    95  
    96  // unregister unregisters the engine associated with the given channel and closes the conduit queue.
    97  func (n *Network) unregister(channel channels.Channel) error {
    98  	con := n.conduits[channel]
    99  	close(con.queue)
   100  	delete(n.conduits, channel)
   101  	return nil
   102  }
   103  
   104  // submit is called when the attached Engine to the channel is sending an event to an
   105  // Engine attached to the same channel on another node or nodes.
   106  // This implementation uses unicast under the hood.
   107  func (n *Network) submit(event interface{}, channel channels.Channel, targetIDs ...flow.Identifier) error {
   108  	var sendErrors *multierror.Error
   109  	for _, targetID := range targetIDs {
   110  		if err := n.unicast(event, channel, targetID); err != nil {
   111  			sendErrors = multierror.Append(sendErrors, fmt.Errorf("could not unicast the event: %w", err))
   112  		}
   113  	}
   114  	return sendErrors.ErrorOrNil()
   115  }
   116  
   117  // unicast is called when the attached Engine to the channel is sending an event to a single target
   118  // Engine attached to the same channel on another node.
   119  func (n *Network) unicast(event interface{}, channel channels.Channel, targetID flow.Identifier) error {
   120  	net, found := n.hub.networks[targetID]
   121  	if !found {
   122  		return fmt.Errorf("could not find target network on hub: %x", targetID)
   123  	}
   124  	con, found := net.conduits[channel]
   125  	if !found {
   126  		return fmt.Errorf("invalid channel (%d) for target ID (%x)", targetID, channel)
   127  	}
   128  
   129  	sender, receiver := n.node, net.node
   130  	block, delay := n.hub.filter(channel, event, sender, receiver)
   131  	// block the message
   132  	if block {
   133  		return nil
   134  	}
   135  
   136  	// no delay, push to the receiver's message queue right away
   137  	if delay == 0 {
   138  		con.queue <- message{originID: n.originID, event: event}
   139  		return nil
   140  	}
   141  
   142  	// use a goroutine to wait and send
   143  	go func(delay time.Duration, senderID flow.Identifier, receiver *Conduit, event interface{}) {
   144  		// sleep in order to simulate the network delay
   145  		time.Sleep(delay)
   146  		con.queue <- message{originID: senderID, event: event}
   147  	}(delay, n.originID, con, event)
   148  
   149  	return nil
   150  }
   151  
   152  // publish is called when the attached Engine is sending an event to a group of Engines attached to the
   153  // same channel on other nodes based on selector.
   154  // In this test helper implementation, publish uses submit method under the hood.
   155  func (n *Network) publish(event interface{}, channel channels.Channel, targetIDs ...flow.Identifier) error {
   156  	return n.submit(event, channel, targetIDs...)
   157  }
   158  
   159  // multicast is called when an Engine attached to the channel is sending an event to a number of randomly chosen
   160  // Engines attached to the same channel on other nodes. The targeted nodes are selected based on the selector.
   161  // In this test helper implementation, multicast uses submit method under the hood.
   162  func (n *Network) multicast(event interface{}, channel channels.Channel, num uint, targetIDs ...flow.Identifier) error {
   163  	var err error
   164  	targetIDs, err = flow.Sample(num, targetIDs...)
   165  	if err != nil {
   166  		return fmt.Errorf("sampling failed: %w", err)
   167  	}
   168  	return n.submit(event, channel, targetIDs...)
   169  }
   170  
   171  type Conduit struct {
   172  	ctx     context.Context
   173  	cancel  context.CancelFunc
   174  	net     *Network
   175  	channel channels.Channel
   176  	queue   chan message
   177  }
   178  
   179  // ReportMisbehavior reports the misbehavior of a node on sending a message to the current node that appears valid
   180  // based on the networking layer but is considered invalid by the current node based on the Flow protocol.
   181  // This method is a no-op in the test helper implementation.
   182  func (c *Conduit) ReportMisbehavior(_ network.MisbehaviorReport) {
   183  	// no-op
   184  }
   185  
   186  var _ network.Conduit = (*Conduit)(nil)
   187  
   188  func (c *Conduit) Submit(event interface{}, targetIDs ...flow.Identifier) error {
   189  	if c.ctx.Err() != nil {
   190  		return fmt.Errorf("conduit closed")
   191  	}
   192  	return c.net.submit(event, c.channel, targetIDs...)
   193  }
   194  
   195  func (c *Conduit) Publish(event interface{}, targetIDs ...flow.Identifier) error {
   196  	if c.ctx.Err() != nil {
   197  		return fmt.Errorf("conduit closed")
   198  	}
   199  	return c.net.publish(event, c.channel, targetIDs...)
   200  }
   201  
   202  func (c *Conduit) Unicast(event interface{}, targetID flow.Identifier) error {
   203  	if c.ctx.Err() != nil {
   204  		return fmt.Errorf("conduit closed")
   205  	}
   206  	return c.net.unicast(event, c.channel, targetID)
   207  }
   208  
   209  func (c *Conduit) Multicast(event interface{}, num uint, targetIDs ...flow.Identifier) error {
   210  	if c.ctx.Err() != nil {
   211  		return fmt.Errorf("conduit closed")
   212  	}
   213  	return c.net.multicast(event, c.channel, num, targetIDs...)
   214  }
   215  
   216  func (c *Conduit) Close() error {
   217  	if c.ctx.Err() != nil {
   218  		return fmt.Errorf("conduit closed")
   219  	}
   220  	c.cancel()
   221  	return c.net.unregister(c.channel)
   222  }
   223  
   224  type message struct {
   225  	originID flow.Identifier
   226  	event    interface{}
   227  }