github.com/aakash4dev/cometbft@v0.38.2/spec/p2p/reactor-api/reactor.md (about)

     1  # Reactor API
     2  
     3  A component has to implement the [`p2p.Reactor` interface][reactor-interface]
     4  in order to use communication services provided by the p2p layer.
     5  This interface is currently the main source of documentation for a reactor.
     6  
     7  The goal of this document is to specify the behaviour of the p2p communication
     8  layer when interacting with a reactor.
     9  So while the [`Reactor interface`][reactor-interface] declares the methods
    10  invoked and determines what the p2p layer expects from a reactor,
    11  this documentation focuses on the **temporal behaviour** that a reactor implementation
    12  should expect from the p2p layer. (That is, in which orders the functions may be called)
    13  
    14  This specification is accompanied by the [`reactor.qnt`](./reactor.qnt) file,
    15  a more comprehensive model of the reactor's operation written in
    16  [Quint][quint-repo], an executable specification language.
    17  The methods declared in the [`Reactor`][reactor-interface] interface are
    18  modeled in Quint, in the form of `pure def` methods, providing some examples of
    19  how they should be implemented.
    20  The behaviour of the p2p layer when interacting with a reactor, by invoking the
    21  interface methods, is modeled in the form of state transitions, or `action`s in
    22  the Quint nomenclature.
    23  
    24  ## Overview
    25  
    26  The following _grammar_ is a simplified representation of the expected sequence of calls
    27  from the p2p layer to a reactor.
    28  Note that the grammar represents events referring to a _single reactor_, while
    29  the p2p layer supports the execution of multiple reactors.
    30  For a more detailed representation of the sequence of calls from the p2p layer
    31  to reactors, please refer to the companion Quint model.
    32  
    33  While useful to provide an overview of the operation of a reactor,
    34  grammars have some limitations in terms of the behaviour they can express.
    35  For instance, the following grammar only represents the management of _a single peer_,
    36  namely of a peer with a given ID which can connect, disconnect, and reconnect
    37  multiple times to the node.
    38  The p2p layer and every reactor should be able to handle multiple distinct peers in parallel.
    39  This means that multiple occurrences of non-terminal `peer-management` of the
    40  grammar below can "run" independently and in parallel, each one referring and
    41  producing events associated to a different peer:
    42  
    43  ```abnf
    44  start           = registration on-start *peer-management on-stop
    45  registration    = get-channels set-switch
    46  
    47  ; Refers to a single peer, a reactor must support multiple concurrent peers
    48  peer-management = init-peer start-peer stop-peer
    49  start-peer      = [*receive] (connected-peer / start-error)
    50  connected-peer  = add-peer *receive
    51  stop-peer       = [peer-error] remove-peer
    52  
    53  ; Service interface
    54  on-start        = %s"OnStart()"
    55  on-stop         = %s"OnStop()"
    56  ; Reactor interface
    57  get-channels    = %s"GetChannels()"
    58  set-switch      = %s"SetSwitch(*Switch)"
    59  init-peer       = %s"InitPeer(Peer)"
    60  add-peer        = %s"AddPeer(Peer)"
    61  remove-peer     = %s"RemovePeer(Peer, reason)"
    62  receive         = %s"Receive(Envelope)"
    63  
    64  ; Errors, for reference
    65  start-error     = %s"log(Error starting peer)"
    66  peer-error      = %s"log(Stopping peer for error)"
    67  ```
    68  
    69  The grammar is written in case-sensitive Augmented Backus–Naur form (ABNF,
    70  specified in [IETF RFC 7405](https://datatracker.ietf.org/doc/html/rfc7405)).
    71  It is inspired on the grammar produced to specify the interaction of CometBFT
    72  with an ABCI++ application, available [here](../../abci/abci%2B%2B_comet_expected_behavior.md).
    73  
    74  ## Registration
    75  
    76  To become a reactor, a component has first to implement the
    77  [`Reactor`][reactor-interface] interface,
    78  then to register the implementation with the p2p layer, using the
    79  `Switch.AddReactor(name string, reactor Reactor)` method,
    80  with a global unique `name` for the reactor.
    81  
    82  The registration must happen before the node, in general, and the p2p layer,
    83  in particular, are started.
    84  In other words, there is no support for registering a reactor on a running node:
    85  reactors must be registered as part of the setup of a node.
    86  
    87  ```abnf
    88  registration    = get-channels set-switch
    89  ```
    90  
    91  The p2p layer retrieves from the reactor a list of channels the reactor is
    92  responsible for, using the `GetChannels()` method.
    93  The reactor implementation should thereafter expect the delivery of every
    94  message received by the p2p layer in the informed channels.
    95  
    96  The second method `SetSwitch(Switch)` concludes the handshake between the
    97  reactor and the p2p layer.
    98  The `Switch` is the main component of the p2p layer, being responsible for
    99  establishing connections with peers and routing messages.
   100  The `Switch` instance provides a number of methods for all registered reactors,
   101  documented in the companion [API for Reactors](./p2p-api.md#switch-api) document.
   102  
   103  ## Service interface
   104  
   105  A reactor must implement the [`Service`](../../../libs/service/service.go) interface,
   106  in particular, a startup `OnStart()` and a shutdown `OnStop()` methods:
   107  
   108  ```abnf
   109  start           = registration on-start *peer-management on-stop
   110  ```
   111  
   112  As part of the startup of a node, all registered reactors are started by the p2p layer.
   113  And when the node is shut down, all registered reactors are stopped by the p2p layer.
   114  Observe that the `Service` interface specification establishes that a service
   115  can be started and stopped only once.
   116  So before being started or once stopped by the p2p layer, the reactor should
   117  not expect any interaction.
   118  
   119  ## Peer management
   120  
   121  The core of a reactor's operation is the interaction with peers or, more
   122  precisely, with companion reactors operating on the same channels in peers connected to the node.
   123  The grammar extract below represents the interaction of the reactor with a
   124  single peer:
   125  
   126  ```abnf
   127  ; Refers to a single peer, a reactor must support multiple concurrent peers
   128  peer-management = init-peer start-peer stop-peer
   129  ```
   130  
   131  The p2p layer informs all registered reactors when it establishes a connection
   132  with a `Peer`, using the `InitPeer(Peer)` method.
   133  When this method is invoked, the `Peer` has not yet been started, namely the
   134  routines for sending messages to and receiving messages from the peer are not running.
   135  This method should be used to initialize state or data related to the new
   136  peer, but not to interact with it.
   137  
   138  The next step is to start the communication routines with the new `Peer`.
   139  As detailed in the following, this procedure may or may not succeed.
   140  In any case, the peer is eventually stopped, which concludes the management of
   141  that `Peer` instance.
   142  
   143  ## Start peer
   144  
   145  Once `InitPeer(Peer)` is invoked for every registered reactor, the p2p layer starts the peer's
   146  communication routines and adds the `Peer` to the set of connected peers.
   147  If both steps are concluded without errors, the reactor's `AddPeer(Peer)` is invoked:
   148  
   149  ```abnf
   150  start-peer      = [*receive] (connected-peer / start-error)
   151  connected-peer  = add-peer *receive
   152  ```
   153  
   154  In case of errors, a message is logged informing that the p2p layer failed to start the peer.
   155  This is not a common scenario and it is only expected to happen when
   156  interacting with a misbehaving or slow peer. A practical example is reported on this
   157  [issue](https://github.com/tendermint/tendermint/pull/9500).
   158  
   159  It is up to the reactor to define how to process the `AddPeer(Peer)` event.
   160  The typical behavior is to start routines that, given some conditions or events,
   161  send messages to the added peer, using the provided `Peer` instance.
   162  The companion [API for Reactors](./p2p-api.md#peer-api) documents the methods
   163  provided by `Peer` instances, available from when they are added to the reactors.
   164  
   165  ## Stop Peer
   166  
   167  The p2p layer informs all registered reactors when it disconnects from a `Peer`,
   168  using the `RemovePeer(Peer, reason)` method:
   169  
   170  ```abnf
   171  stop-peer       = [peer-error] remove-peer
   172  ```
   173  
   174  This method is invoked after the p2p layer has stopped peer's send and receive routines.
   175  Depending of the `reason` for which the peer was stopped, different log
   176  messages can be produced.
   177  After removing a peer from all reactors, the `Peer` instance is also removed from
   178  the set of connected peers.
   179  This enables the same peer to reconnect and `InitPeer(Peer)` to be invoked for
   180  the new connection.
   181  
   182  From the removal of a `Peer` , the reactor should not receive any further message
   183  from the peer and must not try sending messages to the removed peer.
   184  This usually means stopping the routines that were started by the companion
   185  `Add(Peer)` method.
   186  
   187  ## Receive messages
   188  
   189  The main duty of a reactor is to handle incoming messages on the channels it
   190  has registered with the p2p layer.
   191  
   192  The _pre-condition_ for receiving a message from a `Peer` is that the p2p layer
   193  has previously invoked `InitPeer(Peer)`.
   194  This means that the reactor must be able to receive a message from a `Peer`
   195  _before_ `AddPeer(Peer)` is invoked.
   196  This happens because the peer's send and receive routines are started before,
   197  and should be already running when the p2p layer adds the peer to every
   198  registered reactor.
   199  
   200  ```abnf
   201  start-peer      = [*receive] (connected-peer / start-error)
   202  connected-peer  = add-peer *receive
   203  ```
   204  
   205  The most common scenario, however, is to start receiving messages from a peer
   206  after `AddPeer(Peer)` is invoked.
   207  An arbitrary number of messages can be received, until the peer is stopped and
   208  `RemovePeer(Peer)` is invoked.
   209  
   210  When a message is received from a connected peer on any of the channels
   211  registered by the reactor, the p2p layer will deliver the message to the
   212  reactor via the `Receive(Envelope)` method.
   213  The message is packed into an `Envelope` that contains:
   214  
   215  - `ChannelID`: the channel the message belongs to
   216  - `Src`: the source `Peer` handler, from which the message was received
   217  - `Message`: the actual message's payload, unmarshalled using protocol buffers
   218  
   219  Two important observations regarding the implementation of the `Receive` method:
   220  
   221  1. Concurrency: the implementation should consider concurrent invocations of
   222     the `Receive` method carrying messages from different peers, as the
   223     interaction with different peers is independent and messages can be received in parallel.
   224  1. Non-blocking: the implementation of the `Receive` method is expected not to block,
   225     as it is invoked directly by the receive routines.
   226     In other words, while `Receive` does not return, other messages from the
   227     same sender are not delivered to any reactor.
   228  
   229  [reactor-interface]: ../../../p2p/base_reactor.go
   230  [quint-repo]: https://github.com/informalsystems/quint