44package pusher
55
66import (
7+ "context"
78 "fmt"
89
910 "github.com/rs/zerolog"
1011
1112 "github.com/onflow/flow-go/engine"
13+ "github.com/onflow/flow-go/engine/common/fifoqueue"
1214 "github.com/onflow/flow-go/model/flow"
1315 "github.com/onflow/flow-go/model/flow/filter"
1416 "github.com/onflow/flow-go/model/messages"
1517 "github.com/onflow/flow-go/module"
18+ "github.com/onflow/flow-go/module/component"
19+ "github.com/onflow/flow-go/module/irrecoverable"
1620 "github.com/onflow/flow-go/module/metrics"
1721 "github.com/onflow/flow-go/network"
1822 "github.com/onflow/flow-go/network/channels"
@@ -21,30 +25,60 @@ import (
2125 "github.com/onflow/flow-go/utils/logging"
2226)
2327
24- // Engine is the collection pusher engine, which provides access to resources
25- // held by the collection node.
28+ // Engine is part of the Collection Node. It broadcasts finalized collections
29+ // ("collection guarantees") that the cluster generates to Consensus Nodes
30+ // for inclusion in blocks.
2631type Engine struct {
27- unit * engine.Unit
2832 log zerolog.Logger
2933 engMetrics module.EngineMetrics
30- colMetrics module.CollectionMetrics
3134 conduit network.Conduit
3235 me module.Local
3336 state protocol.State
3437 collections storage.Collections
3538 transactions storage.Transactions
39+
40+ notifier engine.Notifier
41+ queue * fifoqueue.FifoQueue
42+
43+ component.Component
44+ cm * component.ComponentManager
3645}
3746
38- func New (log zerolog.Logger , net network.EngineRegistry , state protocol.State , engMetrics module.EngineMetrics , colMetrics module.CollectionMetrics , me module.Local , collections storage.Collections , transactions storage.Transactions ) (* Engine , error ) {
47+ // TODO convert to network.MessageProcessor
48+ var _ network.Engine = (* Engine )(nil )
49+ var _ component.Component = (* Engine )(nil )
50+
51+ // New creates a new pusher engine.
52+ func New (
53+ log zerolog.Logger ,
54+ net network.EngineRegistry ,
55+ state protocol.State ,
56+ engMetrics module.EngineMetrics ,
57+ mempoolMetrics module.MempoolMetrics ,
58+ me module.Local ,
59+ collections storage.Collections ,
60+ transactions storage.Transactions ,
61+ ) (* Engine , error ) {
62+ queue , err := fifoqueue .NewFifoQueue (
63+ 200 , // roughly 1 minute of collections, at 3BPS
64+ fifoqueue .WithLengthObserver (func (len int ) {
65+ mempoolMetrics .MempoolEntries (metrics .ResourceSubmitCollectionGuaranteesQueue , uint (len ))
66+ }),
67+ )
68+ if err != nil {
69+ return nil , fmt .Errorf ("could not create fifoqueue: %w" , err )
70+ }
71+
3972 e := & Engine {
40- unit : engine .NewUnit (),
4173 log : log .With ().Str ("engine" , "pusher" ).Logger (),
4274 engMetrics : engMetrics ,
43- colMetrics : colMetrics ,
4475 me : me ,
4576 state : state ,
4677 collections : collections ,
4778 transactions : transactions ,
79+
80+ notifier : engine .NewNotifier (),
81+ queue : queue ,
4882 }
4983
5084 conduit , err := net .Register (channels .PushGuarantees , e )
@@ -53,88 +87,117 @@ func New(log zerolog.Logger, net network.EngineRegistry, state protocol.State, e
5387 }
5488 e .conduit = conduit
5589
90+ e .cm = component .NewComponentManagerBuilder ().
91+ AddWorker (e .outboundQueueWorker ).
92+ Build ()
93+ e .Component = e .cm
94+
5695 return e , nil
5796}
5897
59- // Ready returns a ready channel that is closed once the engine has fully
60- // started.
61- func (e * Engine ) Ready () <- chan struct {} {
62- return e .unit .Ready ()
98+ // outboundQueueWorker implements a component worker which broadcasts collection guarantees,
99+ // enqueued by the Finalizer upon finalization, to Consensus Nodes.
100+ func (e * Engine ) outboundQueueWorker (ctx irrecoverable.SignalerContext , ready component.ReadyFunc ) {
101+ ready ()
102+
103+ done := ctx .Done ()
104+ wake := e .notifier .Channel ()
105+ for {
106+ select {
107+ case <- done :
108+ return
109+ case <- wake :
110+ err := e .processOutboundMessages (ctx )
111+ if err != nil {
112+ ctx .Throw (err )
113+ }
114+ }
115+ }
63116}
64117
65- // Done returns a done channel that is closed once the engine has fully stopped.
66- func (e * Engine ) Done () <- chan struct {} {
67- return e .unit .Done ()
118+ // processOutboundMessages processes any available messages from the queue.
119+ // Only returns when the queue is empty (or the engine is terminated).
120+ // No errors expected during normal operations.
121+ func (e * Engine ) processOutboundMessages (ctx context.Context ) error {
122+ for {
123+ nextMessage , ok := e .queue .Pop ()
124+ if ! ok {
125+ return nil
126+ }
127+
128+ asSCGMsg , ok := nextMessage .(* messages.SubmitCollectionGuarantee )
129+ if ! ok {
130+ return fmt .Errorf ("invalid message type in pusher engine queue" )
131+ }
132+
133+ err := e .publishCollectionGuarantee (& asSCGMsg .Guarantee )
134+ if err != nil {
135+ return err
136+ }
137+
138+ select {
139+ case <- ctx .Done ():
140+ return nil
141+ default :
142+ }
143+ }
68144}
69145
70146// SubmitLocal submits an event originating on the local node.
71147func (e * Engine ) SubmitLocal (event interface {}) {
72- e . unit . Launch ( func () {
73- err := e . process ( e . me . NodeID (), event )
74- if err != nil {
75- engine . LogError ( e . log , err )
76- }
77- })
148+ ev , ok := event .( * messages. SubmitCollectionGuarantee )
149+ if ok {
150+ e . SubmitCollectionGuarantee ( ev )
151+ } else {
152+ engine . LogError ( e . log , fmt . Errorf ( "invalid message argument to pusher engine" ))
153+ }
78154}
79155
80156// Submit submits the given event from the node with the given origin ID
81157// for processing in a non-blocking manner. It returns instantly and logs
82158// a potential processing error internally when done.
83159func (e * Engine ) Submit (channel channels.Channel , originID flow.Identifier , event interface {}) {
84- e .unit .Launch (func () {
85- err := e .process (originID , event )
86- if err != nil {
87- engine .LogError (e .log , err )
88- }
89- })
160+ engine .LogError (e .log , fmt .Errorf ("pusher engine should only receive local messages on the same node" ))
90161}
91162
92163// ProcessLocal processes an event originating on the local node.
93164func (e * Engine ) ProcessLocal (event interface {}) error {
94- return e .unit .Do (func () error {
95- return e .process (e .me .NodeID (), event )
96- })
165+ ev , ok := event .(* messages.SubmitCollectionGuarantee )
166+ if ok {
167+ e .SubmitCollectionGuarantee (ev )
168+ return nil
169+ } else {
170+ return fmt .Errorf ("invalid message argument to pusher engine" )
171+ }
97172}
98173
99174// Process processes the given event from the node with the given origin ID in
100175// a blocking manner. It returns the potential processing error when done.
101- func (e * Engine ) Process (channel channels.Channel , originID flow.Identifier , event interface {}) error {
102- return e .unit .Do (func () error {
103- return e .process (originID , event )
104- })
176+ func (e * Engine ) Process (channel channels.Channel , originID flow.Identifier , message any ) error {
177+ return fmt .Errorf ("pusher engine should only receive local messages on the same node" )
105178}
106179
107- // process processes events for the pusher engine on the collection node.
108- func (e * Engine ) process (originID flow.Identifier , event interface {}) error {
109- switch ev := event .(type ) {
110- case * messages.SubmitCollectionGuarantee :
111- e .engMetrics .MessageReceived (metrics .EngineCollectionProvider , metrics .MessageSubmitGuarantee )
112- defer e .engMetrics .MessageHandled (metrics .EngineCollectionProvider , metrics .MessageSubmitGuarantee )
113- return e .onSubmitCollectionGuarantee (originID , ev )
114- default :
115- return fmt .Errorf ("invalid event type (%T)" , event )
180+ // SubmitCollectionGuarantee adds a collection guarantee to the engine's queue
181+ // to later be published to consensus nodes.
182+ func (e * Engine ) SubmitCollectionGuarantee (msg * messages.SubmitCollectionGuarantee ) {
183+ if e .queue .Push (msg ) {
184+ e .notifier .Notify ()
185+ } else {
186+ e .engMetrics .OutboundMessageDropped (metrics .EngineCollectionProvider , metrics .MessageCollectionGuarantee )
116187 }
117188}
118189
119- // onSubmitCollectionGuarantee handles submitting the given collection guarantee
120- // to consensus nodes.
121- func (e * Engine ) onSubmitCollectionGuarantee (originID flow.Identifier , req * messages.SubmitCollectionGuarantee ) error {
122- if originID != e .me .NodeID () {
123- return fmt .Errorf ("invalid remote request to submit collection guarantee (from=%x)" , originID )
124- }
125-
126- return e .SubmitCollectionGuarantee (& req .Guarantee )
127- }
128-
129- // SubmitCollectionGuarantee submits the collection guarantee to all consensus nodes.
130- func (e * Engine ) SubmitCollectionGuarantee (guarantee * flow.CollectionGuarantee ) error {
190+ // publishCollectionGuarantee publishes the collection guarantee to all consensus nodes.
191+ // No errors expected during normal operation.
192+ func (e * Engine ) publishCollectionGuarantee (guarantee * flow.CollectionGuarantee ) error {
131193 consensusNodes , err := e .state .Final ().Identities (filter.HasRole [flow.Identity ](flow .RoleConsensus ))
132194 if err != nil {
133- return fmt .Errorf ("could not get consensus nodes: %w" , err )
195+ return fmt .Errorf ("could not get consensus nodes' identities : %w" , err )
134196 }
135197
136- // NOTE: Consensus nodes do not broadcast guarantees among themselves, so it needs that
137- // at least one collection node make a publish to all of them.
198+ // NOTE: Consensus nodes do not broadcast guarantees among themselves. So for the collection to be included,
199+ // at least one collector has to successfully broadcast the collection to consensus nodes. Otherwise, the
200+ // collection is lost, which is acceptable as long as we only lose a small fraction of collections.
138201 err = e .conduit .Publish (guarantee , consensusNodes .NodeIDs ()... )
139202 if err != nil {
140203 return fmt .Errorf ("could not submit collection guarantee: %w" , err )
0 commit comments