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