@@ -144,6 +144,19 @@ class P2PService {
144144 // Forward the raw envelope for any listeners that want it
145145 _envelopeStreamController.add (envelope);
146146
147+ if (envelope.msgType == 'head_exchange' ) {
148+ _handleHeadExchange (envelope);
149+ return ;
150+ }
151+ if (envelope.msgType == 'message_request' ) {
152+ _handleMessageRequest (envelope);
153+ return ;
154+ }
155+ if (envelope.msgType == 'message_response' ) {
156+ _handleMessageResponse (envelope);
157+ return ;
158+ }
159+
147160 // Day-18: sync_request and sync_response bypass GossipLog.
148161 // They are bulk state transfer meta-messages, not causal operations.
149162 if (envelope.msgType == 'sync_request' ) {
@@ -157,12 +170,115 @@ class P2PService {
157170
158171 // All other message types go through GossipLog for causal ordering.
159172 // _gossipLogSub will call _routeValidEnvelope when deps are satisfied.
160- _gossipLog.receive (envelope);
173+ final missingDeps = _gossipLog.receive (envelope);
174+ if (missingDeps.isNotEmpty) {
175+ debugPrint ('[P2P] MISSING_DETECTED: requesting missing deps $missingDeps ' );
176+ _sendMessageRequest (missingDeps);
177+ }
161178 } catch (e) {
162179 debugPrint ('[P2P] Failed to parse incoming message: $e ' );
163180 }
164181 }
165182
183+ void _handleHeadExchange (NetworkEnvelope envelope) {
184+ debugPrint ('[P2P] HEADS_RECEIVED: from ${envelope .originPeer }' );
185+ final peerHeads = (envelope.payload['heads' ] as List <dynamic >? ?? [])
186+ .map ((e) => e.toString ())
187+ .toList ();
188+
189+ final missing = _gossipLog.findMissingMessages (peerHeads);
190+ if (missing.isNotEmpty) {
191+ debugPrint ('[P2P] MISSING_DETECTED: requesting ${missing .length } messages' );
192+ _sendMessageRequest (missing);
193+ }
194+ }
195+
196+ void _handleMessageRequest (NetworkEnvelope envelope) {
197+ final requestedIds = (envelope.payload['requested_ids' ] as List <dynamic >? ?? [])
198+ .map ((e) => e.toString ())
199+ .toList ();
200+
201+ debugPrint ('[P2P] MESSAGE_REQUEST_RECEIVED: for ${requestedIds .length } messages' );
202+
203+ final messagesToSend = _gossipLog.fetchAndSortMessages (requestedIds);
204+ if (messagesToSend.isNotEmpty) {
205+ _sendMessageResponse (messagesToSend);
206+ }
207+ }
208+
209+ void _handleMessageResponse (NetworkEnvelope envelope) {
210+ debugPrint ('[P2P] MESSAGE_RESPONSE_RECEIVED: from ${envelope .originPeer }' );
211+ final messagesList = envelope.payload['messages' ] as List <dynamic >? ?? [];
212+
213+ for (final msgData in messagesList) {
214+ try {
215+ final Map <String , dynamic > msgMap =
216+ msgData is Map <String , dynamic >
217+ ? msgData
218+ : Map <String , dynamic >.from (msgData as Map );
219+
220+ final msgEnvelope = NetworkEnvelope .fromJson (msgMap);
221+
222+ // Add to deduplication cache to prevent re-processing
223+ if (_messageCache.isDuplicate (msgEnvelope.msgId)) {
224+ continue ;
225+ }
226+
227+ // Process through gossip log
228+ final missingDeps = _gossipLog.receive (msgEnvelope);
229+
230+ if (missingDeps.isNotEmpty) {
231+ debugPrint ('[P2P] MISSING_DETECTED: requesting missing deps $missingDeps ' );
232+ _sendMessageRequest (missingDeps);
233+ }
234+ } catch (e) {
235+ debugPrint ('[P2P] Failed to parse message in response: $e ' );
236+ }
237+ }
238+ }
239+
240+ Future <void > _sendMessageRequest (List <String > requestedIds) async {
241+ if (requestedIds.isEmpty) return ;
242+
243+ final envelope = NetworkEnvelope (
244+ msgId: 'msg_req_${DateTime .now ().millisecondsSinceEpoch }' ,
245+ msgType: 'message_request' ,
246+ originPeer: '' ,
247+ timestamp: DateTime .now ().millisecondsSinceEpoch ~ / 1000 ,
248+ payload: {
249+ 'requested_ids' : requestedIds,
250+ },
251+ );
252+
253+ debugPrint ('[P2P] MESSAGE_REQUEST_SENT: requesting ids $requestedIds ' );
254+
255+ // Add to our own dedup cache to prevent self-echo
256+ _messageCache.isDuplicate (envelope.msgId);
257+
258+ await _sendEnvelopeHttp (envelope);
259+ }
260+
261+ Future <void > _sendMessageResponse (List <NetworkEnvelope > messages) async {
262+ final payloadMessages = messages.map ((m) => m.toJson ()).toList ();
263+
264+ final envelope = NetworkEnvelope (
265+ msgId: 'msg_resp_${DateTime .now ().millisecondsSinceEpoch }' ,
266+ msgType: 'message_response' ,
267+ originPeer: '' ,
268+ timestamp: DateTime .now ().millisecondsSinceEpoch ~ / 1000 ,
269+ payload: {
270+ 'messages' : payloadMessages,
271+ },
272+ );
273+
274+ debugPrint ('[P2P] MESSAGE_RESPONSE_SENT: sending ${messages .length } messages' );
275+
276+ // Add to our own dedup cache to prevent self-echo
277+ _messageCache.isDuplicate (envelope.msgId);
278+
279+ await _sendEnvelopeHttp (envelope);
280+ }
281+
166282 /// Routes a causally-validated envelope to the appropriate downstream handler.
167283 ///
168284 /// Called by the GossipLog subscription once all dependencies are satisfied.
0 commit comments