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

     1  // -*- mode: Bluespec; -*-
     2  /*
     3   * Reactor is responsible for handling incoming messages on one or more
     4   * Channel. Switch calls GetChannels when reactor is added to it. When a new
     5   * peer joins our node, InitPeer and AddPeer are called. RemovePeer is called
     6   * when the peer is stopped. Receive is called when a message is received on a
     7   * channel associated with this reactor.
     8   */
     9  // Code: https://github.com/aakash4dev/cometbft/blob/main/p2p/base_reactor.go
    10  module reactor {
    11  
    12  	// Unique ID of a node.
    13  	type NodeID = str
    14  
    15  	/*
    16  	 * Peer is an interface representing a peer connected on a reactor.
    17  	 */
    18  	type Peer = {
    19  		ID: NodeID,
    20  
    21  		// Other fields can be added to represent the p2p operation.
    22  	}
    23  
    24  	// Byte ID used by channels, must be globally unique.
    25  	type Byte = str
    26  
    27  	// Channel configuration.
    28  	type ChannelDescriptor = {
    29  		ID: Byte,
    30  		Priority: int,
    31  	}
    32  
    33  	/*
    34  	 * Envelope contains a message with sender routing info.
    35  	 */
    36  	type Envelope = {
    37  		Src: Peer, // Sender
    38  		Message: str, // Payload
    39  		ChannelID: Byte,
    40  	}
    41  
    42  	// A Routine is used to interact with an active Peer.
    43  	type Routine = {
    44  		name: str,
    45  		peer: Peer,
    46  	}
    47  
    48  	type ReactorState = {
    49  		// Peers that have been initialized but not yet removed.
    50  		// The reactor should expect receiving messages from them.
    51  		peers: Set[NodeID],
    52  
    53  		// The reactor runs multiple routines.
    54  		routines: Set[Routine],
    55  
    56  		// Values: init -> registered -> running -> stopped
    57  		state: str,
    58  
    59  		// Name with which the reactor was registered.
    60  		name: str,
    61  
    62  		// Channels the reactor is responsible for.
    63  		channels: Set[ChannelDescriptor],
    64  	}
    65  
    66  	// Produces a new, uninitialized reactor.
    67  	pure def NewReactor(): ReactorState = {
    68  		{
    69  			peers: Set(),
    70  			routines: Set(),
    71  			state: "init",
    72  			name: "",
    73  			channels: Set(),
    74  		}
    75  	}
    76  
    77  	// Pure definitions below represent the `p2p.Reactor` interface methods:
    78  
    79  	/*
    80  	 * GetChannels returns the list of MConnection.ChannelDescriptor. Make sure
    81  	 * that each ID is unique across all the reactors added to the switch.
    82  	 */
    83  	pure def GetChannels(s: ReactorState): Set[ChannelDescriptor] = {
    84  		s.channels // Static list, configured at initialization.
    85  	}
    86  
    87  	/*
    88  	 * SetSwitch allows setting a switch.
    89  	 */
    90  	pure def SetSwitch(s: ReactorState, switch: bool): ReactorState = {
    91  		s.with("state", "registered")
    92  	}
    93  
    94  	/*
    95  	 * Start the service.
    96  	 * If it's already started or stopped, will return an error.
    97  	 */
    98  	pure def OnStart(s: ReactorState): ReactorState = {
    99  		// Startup procedures should come here.
   100  		s.with("state", "running")
   101  	}
   102  
   103  	/*
   104  	 * Stop the service.
   105  	 * If it's already stopped, will return an error.
   106  	 */
   107  	pure def OnStop(s: ReactorState): ReactorState = {
   108  		// Shutdown procedures should come here.
   109  		s.with("state", "stopped")
   110  	}
   111  
   112  	/*
   113  	 * InitPeer is called by the switch before the peer is started. Use it to
   114  	 * initialize data for the peer (e.g. peer state).
   115  	 */
   116  	pure def InitPeer(s: ReactorState, peer: Peer): (ReactorState, Peer) = {
   117  		// This method can update the received peer, which is returned.
   118  		val updatedPeer = peer
   119  		(s.with("peers", s.peers.union(Set(peer.ID))), updatedPeer)
   120  	}
   121  
   122  	/* 
   123  	 * AddPeer is called by the switch after the peer is added and successfully
   124  	 * started. Use it to start goroutines communicating with the peer.
   125  	 */
   126  	pure def AddPeer(s: ReactorState, peer: Peer): ReactorState = {
   127  		// This method can be used to start routines to handle the peer.
   128  		// Below an example of an arbitrary 'ioRoutine' routine.
   129  		val startedRoutines = Set( {name: "ioRoutine", peer: peer} )
   130  		s.with("routines", s.routines.union(startedRoutines))
   131  	}
   132  
   133  	/*
   134  	 * RemovePeer is called by the switch when the peer is stopped (due to error
   135  	 * or other reason).
   136  	 */
   137  	pure def RemovePeer(s: ReactorState, peer: Peer, reason: str): ReactorState = {
   138  		// This method should stop routines created by `AddPeer(Peer)`.
   139  		val stoppedRoutines = s.routines.filter(r => r.peer.ID == peer.ID)
   140  		s.with("peers", s.peers.exclude(Set(peer.ID)))
   141  		 .with("routines", s.routines.exclude(stoppedRoutines))
   142  	}
   143  
   144  	/*
   145  	 * Receive is called by the switch when an envelope is received from any connected
   146  	 * peer on any of the channels registered by the reactor.
   147  	 */
   148  	pure def Receive(s: ReactorState, e: Envelope): ReactorState = {
   149  		// This method should process the message payload: e.Message.
   150  		s
   151  	}
   152  
   153  	// Global state
   154  
   155  	// Reactors are uniquely identified by their names.
   156  	var reactors: str -> ReactorState
   157  
   158  	// Reactor (name) assigned to each channel ID.
   159  	var reactorsByCh: Byte -> str
   160  
   161  	// Helper action to (only) update the state of a given reactor.
   162  	action updateReactorTo(reactor: ReactorState): bool = all {
   163  		reactors' = reactors.set(reactor.name, reactor),
   164  		reactorsByCh' = reactorsByCh
   165  	}
   166  
   167  	// State transitions performed by the p2p layer, invoking `p2p.Reactor` methods:
   168  
   169  	// Code: Switch.AddReactor(name string, reactor Reactor)
   170  	action register(name: str, reactor: ReactorState): bool = all {
   171  		reactor.state == "init",
   172  		// Assign the reactor as responsible for its channel IDs, which
   173  		// should not be already assigned to another reactor.
   174  		val chIDs = reactor.GetChannels().map(c => c.ID)
   175  		all {
   176  			size(chIDs.intersect(reactorsByCh.keys())) == 0,
   177  			reactorsByCh' = reactorsByCh.keys().union(chIDs).
   178  					mapBy(id => if (id.in(chIDs)) name
   179  						    else reactorsByCh.get(id)),
   180  		},
   181  		// Register the reactor by its name, which must be unique.
   182  		not(name.in(reactors.keys())),
   183  		reactors' = reactors.put(name,
   184  			reactor.SetSwitch(true).with("name", name))
   185  	}
   186  
   187  	// Code: Switch.OnStart()
   188  	action start(reactor: ReactorState): bool = all {
   189  		reactor.state == "registered",
   190  		updateReactorTo(reactor.OnStart())
   191  	}
   192  
   193  	// Code: Switch.addPeer(p Peer): preamble
   194  	action initPeer(reactor: ReactorState, peer: Peer): bool = all {
   195  		reactor.state == "running",
   196  		not(peer.ID.in(reactor.peers)),
   197  		updateReactorTo(reactor.InitPeer(peer)._1)
   198  	}
   199  
   200  	// Code: Switch.addPeer(p Peer): conclusion
   201  	action addPeer(reactor: ReactorState, peer: Peer): bool = all {
   202  		reactor.state == "running",
   203  		peer.ID.in(reactor.peers), // InitPeer(peer) and not RemovePeer(peer)
   204  		reactor.routines.filter(r => r.peer.ID == peer.ID).size() == 0,
   205  		updateReactorTo(reactor.AddPeer(peer))
   206  	}
   207  
   208  	// Code: Switch.stopAndRemovePeer(peer Peer, reason interface{})
   209  	action removePeer(reactor: ReactorState, peer: Peer, reason: str): bool = all {
   210  		reactor.state == "running",
   211  		peer.ID.in(reactor.peers), // InitPeer(peer) and not RemovePeer(peer)
   212  		// Routines might not be started, namely: not AddPeer(peer)
   213  		// Routines could also be already stopped if Peer has erroed.
   214  		updateReactorTo(reactor.RemovePeer(peer, reason))
   215  	}
   216  
   217  	// Code: Peer type, onReceive := func(chID byte, msgBytes []byte)
   218  	action receive(reactor: ReactorState, e: Envelope): bool = all {
   219  		reactor.state == "running",
   220  		// The message's sender is an active peer
   221  		e.Src.ID.in(reactor.peers),
   222  		// Reactor is assigned to the message's channel ID
   223  		e.ChannelID.in(reactorsByCh.keys()),
   224  		reactorsByCh.get(e.ChannelID) == reactor.name,
   225  		reactor.GetChannels().exists(c => c.ID == e.ChannelID),
   226  		updateReactorTo(reactor.Receive(e))
   227  	}
   228  
   229  	// Code: Switch.OnStop()
   230  	action stop(reactor: ReactorState): bool = all {
   231  		reactor.state == "running",
   232  		// Either no peer was added or all peers were removed
   233  		reactor.peers.size() == 0,
   234  		updateReactorTo(reactor.OnStop())
   235  	}
   236  
   237  	// Simulation support
   238  
   239  	action init = all {
   240  		reactors' = Map(),
   241  		reactorsByCh' = Map(),
   242  	}
   243  
   244  	// Modelled reactor configuration
   245  	pure val reactorName = "myReactor"
   246  	pure val reactorChannels = Set({ID: "3", Priority: 1}, {ID: "7", Priority: 2})
   247  
   248  	// For retro-compatibility: the state of the modelled reactor
   249  	def state(): ReactorState = {
   250  		reactors.get(reactorName)
   251  	}
   252  
   253  	pure val samplePeers = Set({ID: "p1"}, {ID: "p3"})
   254  	pure val sampleChIDs = Set("1", "3", "7") // ChannelID 1 not registered
   255  	pure val sampleMsgs  = Set("ping", "pong")
   256  
   257  	action step = any {
   258  		register(reactorName, NewReactor.with("channels", reactorChannels)),
   259  		val reactor = reactors.get(reactorName)
   260  		any {
   261  			reactor.start(),
   262  			reactor.stop(),
   263  			nondet peer = oneOf(samplePeers)
   264  			any {
   265  				// Peer-specific actions
   266  				reactor.initPeer(peer),
   267  				reactor.addPeer(peer),
   268  				reactor.removePeer(peer, "no reason"),
   269  				reactor.receive({Src: peer,
   270  					ChannelID: oneOf(sampleChIDs),
   271  					Message: oneOf(sampleMsgs)}),
   272  			}
   273  		}
   274  	}
   275  
   276  }