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 }