Identify Push Protocol Demo

This example demonstrates how to use the libp2p identify-push protocol, which allows nodes to proactively push their identity information to peers when it changes.

$ python -m pip install libp2p
Collecting libp2p
...
Successfully installed libp2p-x.x.x
$ identify-push-demo
==== Starting Identify-Push Example ====

Host 1 listening on /ip4/127.0.0.1/tcp/xxxxx/p2p/QmAbCdEfGhIjKlMnOpQrStUvWxYz
Peer ID: QmAbCdEfGhIjKlMnOpQrStUvWxYz
Host 2 listening on /ip4/127.0.0.1/tcp/xxxxx/p2p/QmZyXwVuTaBcDeRsSkJpOpWrSt
Peer ID: QmZyXwVuTaBcDeRsSkJpOpWrSt

Connecting Host 2 to Host 1...
Host 2 successfully connected to Host 1

Host 1 pushing identify information to Host 2...
Identify push completed successfully!

Example completed successfully!

There is also a more interactive version of the example which runs as separate listener and dialer processes:

$ identify-push-listener-dialer-demo

==== Starting Identify-Push Listener on port 8888 ====

Listener host ready!
Listening on: /ip4/127.0.0.1/tcp/8888/p2p/QmUiN4R3fNrCoQugGgmmb3v35neMEjKFNrsbNGVDsRHWpM
Peer ID: QmUiN4R3fNrCoQugGgmmb3v35neMEjKFNrsbNGVDsRHWpM

Run dialer with command:
identify-push-listener-dialer-demo -d /ip4/127.0.0.1/tcp/8888/p2p/QmUiN4R3fNrCoQugGgmmb3v35neMEjKFNrsbNGVDsRHWpM

Waiting for incoming connections... (Ctrl+C to exit)

Copy the line that starts with identify-push-listener-dialer-demo -d ..., open a new terminal in the same folder and paste it in:

$ identify-push-listener-dialer-demo -d /ip4/127.0.0.1/tcp/8888/p2p/QmUiN4R3fNrCoQugGgmmb3v35neMEjKFNrsbNGVDsRHWpM

==== Starting Identify-Push Dialer on port 8889 ====

Dialer host ready!
Listening on: /ip4/127.0.0.1/tcp/8889/p2p/QmZyXwVuTaBcDeRsSkJpOpWrSt

Connecting to peer: QmUiN4R3fNrCoQugGgmmb3v35neMEjKFNrsbNGVDsRHWpM
Successfully connected to listener!

Pushing identify information to listener...
Identify push completed successfully!

Example completed successfully!

The identify-push protocol enables libp2p nodes to proactively notify their peers when their metadata changes, such as supported protocols or listening addresses. This helps maintain an up-to-date view of the network without requiring regular polling.

The full source code for these examples is below:

Basic example:

  1#!/usr/bin/env python3
  2"""
  3Example demonstrating the identify/push protocol.
  4
  5This example shows how to:
  61. Set up a host with the identify/push protocol handler
  72. Connect to another peer
  83. Push identify information to the peer
  94. Receive and process identify/push messages
 10"""
 11
 12import logging
 13
 14import multiaddr
 15import trio
 16
 17from libp2p import (
 18    new_host,
 19)
 20from libp2p.abc import (
 21    INetStream,
 22)
 23from libp2p.crypto.secp256k1 import (
 24    create_new_key_pair,
 25)
 26from libp2p.custom_types import (
 27    TProtocol,
 28)
 29from libp2p.identity.identify.pb.identify_pb2 import (
 30    Identify,
 31)
 32from libp2p.identity.identify_push import (
 33    ID_PUSH,
 34    push_identify_to_peer,
 35)
 36from libp2p.peer.peerinfo import (
 37    info_from_p2p_addr,
 38)
 39from libp2p.utils.address_validation import (
 40    get_available_interfaces,
 41)
 42
 43# Configure logging
 44logger = logging.getLogger(__name__)
 45
 46
 47def create_custom_identify_handler(host, host_name: str):
 48    """Create a custom identify handler that displays received information."""
 49
 50    async def handle_identify(stream: INetStream) -> None:
 51        peer_id = stream.muxed_conn.peer_id
 52        print(f"\n🔍 {host_name} received identify request from peer: {peer_id}")
 53
 54        # Get the standard identify response using the existing function
 55        from libp2p.identity.identify.identify import (
 56            _mk_identify_protobuf,
 57            _remote_address_to_multiaddr,
 58        )
 59
 60        # Get observed address
 61        observed_multiaddr = None
 62        try:
 63            remote_address = stream.get_remote_address()
 64            if remote_address:
 65                observed_multiaddr = _remote_address_to_multiaddr(remote_address)
 66        except Exception:
 67            pass
 68
 69        # Build the identify protobuf
 70        identify_msg = _mk_identify_protobuf(host, observed_multiaddr)
 71        response_data = identify_msg.SerializeToString()
 72
 73        print(f"   📋 {host_name} identify information:")
 74        if identify_msg.HasField("protocol_version"):
 75            print(f"      Protocol Version: {identify_msg.protocol_version}")
 76        if identify_msg.HasField("agent_version"):
 77            print(f"      Agent Version: {identify_msg.agent_version}")
 78        if identify_msg.HasField("public_key"):
 79            print(f"      Public Key: {identify_msg.public_key.hex()[:16]}...")
 80        if identify_msg.listen_addrs:
 81            print("      Listen Addresses:")
 82            for addr_bytes in identify_msg.listen_addrs:
 83                addr = multiaddr.Multiaddr(addr_bytes)
 84                print(f"        - {addr}")
 85        if identify_msg.protocols:
 86            print("      Supported Protocols:")
 87            for protocol in identify_msg.protocols:
 88                print(f"        - {protocol}")
 89
 90        # Send the response
 91        await stream.write(response_data)
 92        await stream.close()
 93
 94    return handle_identify
 95
 96
 97def create_custom_identify_push_handler(host, host_name: str):
 98    """Create a custom identify/push handler that displays received information."""
 99
100    async def handle_identify_push(stream: INetStream) -> None:
101        peer_id = stream.muxed_conn.peer_id
102        print(f"\n📤 {host_name} received identify/push from peer: {peer_id}")
103
104        try:
105            # Read the identify message using the utility function
106            from libp2p.utils.varint import read_length_prefixed_protobuf
107
108            data = await read_length_prefixed_protobuf(stream, use_varint_format=True)
109
110            # Parse the identify message
111            identify_msg = Identify()
112            identify_msg.ParseFromString(data)
113
114            print("   📋 Received identify information:")
115            if identify_msg.HasField("protocol_version"):
116                print(f"      Protocol Version: {identify_msg.protocol_version}")
117            if identify_msg.HasField("agent_version"):
118                print(f"      Agent Version: {identify_msg.agent_version}")
119            if identify_msg.HasField("public_key"):
120                print(f"      Public Key: {identify_msg.public_key.hex()[:16]}...")
121            if identify_msg.HasField("observed_addr") and identify_msg.observed_addr:
122                observed_addr = multiaddr.Multiaddr(identify_msg.observed_addr)
123                print(f"      Observed Address: {observed_addr}")
124            if identify_msg.listen_addrs:
125                print("      Listen Addresses:")
126                for addr_bytes in identify_msg.listen_addrs:
127                    addr = multiaddr.Multiaddr(addr_bytes)
128                    print(f"        - {addr}")
129            if identify_msg.protocols:
130                print("      Supported Protocols:")
131                for protocol in identify_msg.protocols:
132                    print(f"        - {protocol}")
133
134            # Update the peerstore with the new information
135            from libp2p.identity.identify_push.identify_push import (
136                _update_peerstore_from_identify,
137            )
138
139            await _update_peerstore_from_identify(
140                host.get_peerstore(), peer_id, identify_msg
141            )
142
143            print(f"   ✅ {host_name} updated peerstore with new information")
144
145        except Exception as e:
146            print(f"   ❌ Error processing identify/push: {e}")
147        finally:
148            await stream.close()
149
150    return handle_identify_push
151
152
153async def display_peerstore_info(host, host_name: str, peer_id, description: str):
154    """Display peerstore information for a specific peer."""
155    peerstore = host.get_peerstore()
156
157    try:
158        addrs = peerstore.addrs(peer_id)
159    except Exception:
160        addrs = []
161
162    try:
163        protocols = peerstore.get_protocols(peer_id)
164    except Exception:
165        protocols = []
166
167    print(f"\n📚 {host_name} peerstore for {description}:")
168    print(f"   Peer ID: {peer_id}")
169    if addrs:
170        print("   Addresses:")
171        for addr in addrs:
172            print(f"     - {addr}")
173    else:
174        print("   Addresses: None")
175
176    if protocols:
177        print("   Protocols:")
178        for protocol in protocols:
179            print(f"     - {protocol}")
180    else:
181        print("   Protocols: None")
182
183
184async def main() -> None:
185    print("\n==== Starting Enhanced Identify-Push Example ====\n")
186
187    # Create key pairs for the two hosts
188    key_pair_1 = create_new_key_pair()
189    key_pair_2 = create_new_key_pair()
190
191    # Create the first host
192    host_1 = new_host(key_pair=key_pair_1)
193
194    # Set up custom identify and identify/push handlers
195    host_1.set_stream_handler(
196        TProtocol("/ipfs/id/1.0.0"), create_custom_identify_handler(host_1, "Host 1")
197    )
198    host_1.set_stream_handler(
199        ID_PUSH, create_custom_identify_push_handler(host_1, "Host 1")
200    )
201
202    # Create the second host
203    host_2 = new_host(key_pair=key_pair_2)
204
205    # Set up custom identify and identify/push handlers
206    host_2.set_stream_handler(
207        TProtocol("/ipfs/id/1.0.0"), create_custom_identify_handler(host_2, "Host 2")
208    )
209    host_2.set_stream_handler(
210        ID_PUSH, create_custom_identify_push_handler(host_2, "Host 2")
211    )
212
213    # Start listening on available interfaces using random ports
214    listen_addrs_1 = get_available_interfaces(0)  # 0 for random port
215    listen_addrs_2 = get_available_interfaces(0)  # 0 for random port
216
217    async with (
218        host_1.run(listen_addrs_1),
219        host_2.run(listen_addrs_2),
220        trio.open_nursery() as nursery,
221    ):
222        # Start the peer-store cleanup task
223        nursery.start_soon(host_1.get_peerstore().start_cleanup_task, 60)
224        nursery.start_soon(host_2.get_peerstore().start_cleanup_task, 60)
225
226        # Get the addresses of both hosts
227        addr_1 = host_1.get_addrs()[0]
228        addr_2 = host_2.get_addrs()[0]
229
230        print("🏠 Host Configuration:")
231        print(f"   Host 1: {addr_1}")
232        print(f"   Host 1 Peer ID: {host_1.get_id().pretty()}")
233        print(f"   Host 2: {addr_2}")
234        print(f"   Host 2 Peer ID: {host_2.get_id().pretty()}")
235
236        print("\n🔗 Connecting Host 2 to Host 1...")
237
238        # Connect host_2 to host_1
239        peer_info = info_from_p2p_addr(addr_1)
240        await host_2.connect(peer_info)
241        print("✅ Host 2 successfully connected to Host 1")
242
243        # Run the identify protocol from host_2 to host_1
244        print("\n🔄 Running identify protocol (Host 2 → Host 1)...")
245        from libp2p.identity.identify.identify import ID as IDENTIFY_PROTOCOL_ID
246
247        stream = await host_2.new_stream(host_1.get_id(), (IDENTIFY_PROTOCOL_ID,))
248        response = await stream.read()
249        await stream.close()
250
251        # Run the identify protocol from host_1 to host_2
252        print("\n🔄 Running identify protocol (Host 1 → Host 2)...")
253        stream = await host_1.new_stream(host_2.get_id(), (IDENTIFY_PROTOCOL_ID,))
254        response = await stream.read()
255        await stream.close()
256
257        # Update Host 1's peerstore with Host 2's addresses
258        identify_msg = Identify()
259        identify_msg.ParseFromString(response)
260        peerstore_1 = host_1.get_peerstore()
261        peer_id_2 = host_2.get_id()
262        for addr_bytes in identify_msg.listen_addrs:
263            maddr = multiaddr.Multiaddr(addr_bytes)
264            peerstore_1.add_addr(peer_id_2, maddr, ttl=3600)
265
266        # Display peerstore information before push
267        await display_peerstore_info(
268            host_1, "Host 1", peer_id_2, "Host 2 (before push)"
269        )
270
271        # Push identify information from host_1 to host_2
272        print("\n📤 Host 1 pushing identify information to Host 2...")
273
274        try:
275            success = await push_identify_to_peer(host_1, host_2.get_id())
276
277            if success:
278                print("✅ Identify push completed successfully!")
279            else:
280                print("⚠️  Identify push didn't complete successfully")
281
282        except Exception as e:
283            print(f"❌ Error during identify push: {str(e)}")
284
285        # Give a moment for the identify/push processing to complete
286        await trio.sleep(0.5)
287
288        # Display peerstore information after push
289        await display_peerstore_info(host_1, "Host 1", peer_id_2, "Host 2 (after push)")
290        await display_peerstore_info(
291            host_2, "Host 2", host_1.get_id(), "Host 1 (after push)"
292        )
293
294        # Give more time for background tasks to finish and connections to stabilize
295        print("\n⏳ Waiting for background tasks to complete...")
296        await trio.sleep(1.0)
297
298        # Gracefully close connections to prevent connection errors
299        print("🔌 Closing connections...")
300        await host_2.disconnect(host_1.get_id())
301        await trio.sleep(0.2)
302
303        print("\n🎉 Example completed successfully!")
304
305
306if __name__ == "__main__":
307    trio.run(main)
308
309
310def run_main():
311    """Non-async entry point for the console script."""
312    trio.run(main)

Listener/Dialer example:

  1#!/usr/bin/env python3
  2"""
  3Example demonstrating the identify/push protocol with separate listener and dialer
  4roles.
  5
  6This example shows how to:
  71. Set up a listener host with the identify/push protocol handler
  82. Connect to the listener from a dialer peer
  93. Push identify information to the listener
 104. Receive and process identify/push messages
 11
 12Usage:
 13    # First run this script as a listener (default port 8888):
 14    python identify_push_listener_dialer.py
 15
 16    # Then in another console, run as a dialer (default port 8889):
 17    python identify_push_listener_dialer.py -d /ip4/[HOST_IP]/tcp/8888/p2p/PEER_ID
 18    (where PEER_ID is the peer ID displayed by the listener)
 19"""
 20
 21import argparse
 22import logging
 23import sys
 24
 25import multiaddr
 26from multiaddr import (
 27    Multiaddr,
 28)
 29import trio
 30
 31from libp2p import (
 32    new_host,
 33)
 34from libp2p.abc import (
 35    INetStream,
 36)
 37from libp2p.crypto.secp256k1 import (
 38    create_new_key_pair,
 39)
 40from libp2p.identity.identify import (
 41    ID as ID_IDENTIFY,
 42    identify_handler_for,
 43)
 44from libp2p.identity.identify.identify import (
 45    _remote_address_to_multiaddr,
 46)
 47from libp2p.identity.identify.pb.identify_pb2 import (
 48    Identify,
 49)
 50from libp2p.identity.identify_push import (
 51    ID_PUSH as ID_IDENTIFY_PUSH,
 52    identify_push_handler_for,
 53    push_identify_to_peer,
 54)
 55from libp2p.peer.peerinfo import (
 56    info_from_p2p_addr,
 57)
 58
 59# Configure minimal logging
 60logging.basicConfig(level=logging.WARNING)
 61logging.getLogger("multiaddr").setLevel(logging.WARNING)
 62logging.getLogger("libp2p").setLevel(logging.WARNING)
 63
 64# Configure logging
 65logger = logging.getLogger("libp2p.identity.identify-push-example")
 66
 67
 68def custom_identify_push_handler_for(host, use_varint_format: bool = True):
 69    """
 70    Create a custom handler for the identify/push protocol that logs and prints
 71    the identity information received from the dialer.
 72
 73    Args:
 74        host: The libp2p host
 75        use_varint_format: If True, expect length-prefixed format; if False, expect
 76            raw protobuf
 77
 78    """
 79
 80    async def handle_identify_push(stream: INetStream) -> None:
 81        peer_id = stream.muxed_conn.peer_id
 82
 83        # Get remote address information
 84        try:
 85            remote_address = stream.get_remote_address()
 86            if remote_address:
 87                observed_multiaddr = _remote_address_to_multiaddr(remote_address)
 88                logger.info(
 89                    "Connection from remote peer %s, address: %s, multiaddr: %s",
 90                    peer_id,
 91                    remote_address,
 92                    observed_multiaddr,
 93                )
 94                print(f"\n🔗 Received identify/push request from peer: {peer_id}")
 95                # Add the peer ID to create a complete multiaddr
 96                complete_multiaddr = f"{observed_multiaddr}/p2p/{peer_id}"
 97                print(f"   Remote address: {complete_multiaddr}")
 98        except Exception as e:
 99            logger.error("Error getting remote address: %s", e)
100            print(f"\n🔗 Received identify/push request from peer: {peer_id}")
101
102        try:
103            # Use the utility function to read the protobuf message
104            from libp2p.utils.varint import read_length_prefixed_protobuf
105
106            data = await read_length_prefixed_protobuf(stream, use_varint_format)
107
108            identify_msg = Identify()
109            identify_msg.ParseFromString(data)
110
111            # Log and print the identify information
112            logger.info("Received identify/push from peer %s", peer_id)
113            print(f"\n==== Received identify/push from peer {peer_id} ====")
114
115            if identify_msg.HasField("protocol_version"):
116                logger.info("  Protocol Version: %s", identify_msg.protocol_version)
117                print(f"  Protocol Version: {identify_msg.protocol_version}")
118
119            if identify_msg.HasField("agent_version"):
120                logger.info("  Agent Version: %s", identify_msg.agent_version)
121                print(f"  Agent Version: {identify_msg.agent_version}")
122
123            if identify_msg.HasField("public_key"):
124                logger.info(
125                    "  Public Key: %s", identify_msg.public_key.hex()[:16] + "..."
126                )
127                print(f"  Public Key: {identify_msg.public_key.hex()[:16]}...")
128
129            if identify_msg.listen_addrs:
130                addrs = [Multiaddr(addr) for addr in identify_msg.listen_addrs]
131                logger.info("  Listen Addresses: %s", addrs)
132                print("  Listen Addresses:")
133                for addr in addrs:
134                    print(f"    - {addr}")
135
136            if identify_msg.HasField("observed_addr") and identify_msg.observed_addr:
137                observed_addr = Multiaddr(identify_msg.observed_addr)
138                logger.info("  Observed Address: %s", observed_addr)
139                print(f"  Observed Address: {observed_addr}")
140
141            if identify_msg.protocols:
142                logger.info("  Protocols: %s", identify_msg.protocols)
143                print("  Protocols:")
144                for protocol in identify_msg.protocols:
145                    print(f"    - {protocol}")
146
147            # Update the peerstore with the new information as usual
148            peerstore = host.get_peerstore()
149            from libp2p.identity.identify_push.identify_push import (
150                _update_peerstore_from_identify,
151            )
152
153            await _update_peerstore_from_identify(peerstore, peer_id, identify_msg)
154
155            logger.info("Successfully processed identify/push from peer %s", peer_id)
156            print(f"✅ Successfully processed identify/push from peer {peer_id}")
157
158        except Exception as e:
159            error_msg = str(e)
160            logger.error(
161                "Error processing identify/push from %s: %s", peer_id, error_msg
162            )
163            print(f"\nError processing identify/push from {peer_id}: {error_msg}")
164
165            # Check for specific format mismatch errors
166            if (
167                "Error parsing message" in error_msg
168                or "DecodeError" in error_msg
169                or "ParseFromString" in error_msg
170            ):
171                print("\n" + "=" * 60)
172                print("FORMAT MISMATCH DETECTED!")
173                print("=" * 60)
174                if use_varint_format:
175                    print(
176                        "You are using length-prefixed format (default) but the "
177                        "dialer is using raw protobuf format."
178                    )
179                    print("\nTo fix this, run the dialer with the --raw-format flag:")
180                    print(
181                        "identify-push-listener-dialer-demo --raw-format -d <ADDRESS>"
182                    )
183                else:
184                    print("You are using raw protobuf format but the dialer")
185                    print("is using length-prefixed format (default).")
186                    print(
187                        "\nTo fix this, run the dialer without the --raw-format flag:"
188                    )
189                    print("identify-push-listener-dialer-demo -d <ADDRESS>")
190                print("=" * 60)
191        finally:
192            # Close the stream after processing
193            await stream.close()
194
195    return handle_identify_push
196
197
198async def run_listener(
199    port: int, use_varint_format: bool = True, raw_format_flag: bool = False
200) -> None:
201    """Run a host in listener mode."""
202    from libp2p.utils.address_validation import find_free_port, get_available_interfaces
203
204    if port <= 0:
205        port = find_free_port()
206
207    format_name = "length-prefixed" if use_varint_format else "raw protobuf"
208    print(
209        f"\n==== Starting Identify-Push Listener on port {port} "
210        f"(using {format_name} format) ====\n"
211    )
212
213    # Create key pair for the listener
214    key_pair = create_new_key_pair()
215
216    # Create the listener host
217    host = new_host(key_pair=key_pair)
218
219    # Set up the identify and identify/push handlers with specified format
220    host.set_stream_handler(
221        ID_IDENTIFY, identify_handler_for(host, use_varint_format=use_varint_format)
222    )
223    host.set_stream_handler(
224        ID_IDENTIFY_PUSH,
225        custom_identify_push_handler_for(host, use_varint_format=use_varint_format),
226    )
227
228    # Start listening on all available interfaces
229    listen_addrs = get_available_interfaces(port)
230
231    try:
232        async with host.run(listen_addrs):
233            all_addrs = host.get_addrs()
234            logger.info("Listener host ready!")
235            print("Listener host ready!")
236
237            logger.info("Listener ready, listening on:")
238            print("Listener ready, listening on:")
239            for addr in all_addrs:
240                logger.info(f"{addr}")
241                print(f"{addr}")
242
243            logger.info(f"Peer ID: {host.get_id().pretty()}")
244            print(f"Peer ID: {host.get_id().pretty()}")
245
246            # Use the first address as the default for the dialer command
247            default_addr = all_addrs[0]
248            print("\nRun this from the same folder in another console:")
249            if raw_format_flag:
250                print(
251                    f"identify-push-listener-dialer-demo -d {default_addr} --raw-format"
252                )
253            else:
254                print(f"identify-push-listener-dialer-demo -d {default_addr}")
255            print("\nWaiting for incoming identify/push requests... (Ctrl+C to exit)")
256
257            # Keep running until interrupted
258            try:
259                await trio.sleep_forever()
260            except KeyboardInterrupt:
261                print("\n🛑 Shutting down listener...")
262                logger.info("Listener interrupted by user")
263                return
264    except Exception as e:
265        logger.error(f"Listener error: {e}")
266        raise
267
268
269async def run_dialer(
270    port: int, destination: str, use_varint_format: bool = True
271) -> None:
272    """Run a host in dialer mode that connects to a listener."""
273    format_name = "length-prefixed" if use_varint_format else "raw protobuf"
274    print(
275        f"\n==== Starting Identify-Push Dialer on port {port} "
276        f"(using {format_name} format) ====\n"
277    )
278
279    # Create key pair for the dialer
280    key_pair = create_new_key_pair()
281
282    # Create the dialer host
283    host = new_host(key_pair=key_pair)
284
285    # Set up the identify and identify/push handlers with specified format
286    host.set_stream_handler(
287        ID_IDENTIFY, identify_handler_for(host, use_varint_format=use_varint_format)
288    )
289    host.set_stream_handler(
290        ID_IDENTIFY_PUSH,
291        identify_push_handler_for(host, use_varint_format=use_varint_format),
292    )
293
294    # Start listening on available interfaces
295    from libp2p.utils.address_validation import get_available_interfaces
296
297    listen_addrs = get_available_interfaces(port)
298
299    async with host.run(listen_addrs):
300        logger.info("Dialer host ready!")
301        print("Dialer host ready!")
302
303        logger.info(f"Listening on: {host.get_addrs()[0]}")
304        print(f"Listening on: {host.get_addrs()[0]}")
305
306        # Parse the destination multiaddress and connect to the listener
307        maddr = multiaddr.Multiaddr(destination)
308        peer_info = info_from_p2p_addr(maddr)
309        logger.info(f"Connecting to peer: {peer_info.peer_id}")
310        print(f"\nConnecting to peer: {peer_info.peer_id}")
311
312        try:
313            await host.connect(peer_info)
314            logger.info("Successfully connected to listener!")
315            print("✅ Successfully connected to listener!")
316            print(f"   Connected to: {peer_info.peer_id}")
317            print(f"   Full address: {destination}")
318
319            # Push identify information to the listener
320            logger.info("Pushing identify information to listener...")
321            print("\nPushing identify information to listener...")
322
323            try:
324                # Call push_identify_to_peer which returns a boolean
325                success = await push_identify_to_peer(
326                    host, peer_info.peer_id, use_varint_format=use_varint_format
327                )
328
329                if success:
330                    logger.info("Identify push completed successfully!")
331                    print("✅ Identify push completed successfully!")
332
333                    logger.info("Example completed successfully!")
334                    print("\nExample completed successfully!")
335                else:
336                    logger.warning("Identify push didn't complete successfully.")
337                    print("\nWarning: Identify push didn't complete successfully.")
338
339                    logger.warning("Example completed with warnings.")
340                    print("Example completed with warnings.")
341            except Exception as e:
342                error_msg = str(e)
343                logger.error(f"Error during identify push: {error_msg}")
344                print(f"\nError during identify push: {error_msg}")
345
346                # Check for specific format mismatch errors
347                if (
348                    "Error parsing message" in error_msg
349                    or "DecodeError" in error_msg
350                    or "ParseFromString" in error_msg
351                ):
352                    print("\n" + "=" * 60)
353                    print("FORMAT MISMATCH DETECTED!")
354                    print("=" * 60)
355                    if use_varint_format:
356                        print(
357                            "You are using length-prefixed format (default) but the "
358                            "listener is using raw protobuf format."
359                        )
360                        print(
361                            "\nTo fix this, run the dialer with the --raw-format flag:"
362                        )
363                        print(
364                            f"identify-push-listener-dialer-demo --raw-format -d "
365                            f"{destination}"
366                        )
367                    else:
368                        print("You are using raw protobuf format but the listener")
369                        print("is using length-prefixed format (default).")
370                        print(
371                            "\nTo fix this, run the dialer without the --raw-format "
372                            "flag:"
373                        )
374                        print(f"identify-push-listener-dialer-demo -d {destination}")
375                    print("=" * 60)
376
377                logger.error("Example completed with errors.")
378                print("Example completed with errors.")
379                # Continue execution despite the push error
380
381        except Exception as e:
382            error_msg = str(e)
383            if "unable to connect" in error_msg or "SwarmException" in error_msg:
384                print(f"\n❌ Cannot connect to peer: {peer_info.peer_id}")
385                print(f"   Address: {destination}")
386                print(f"   Error: {error_msg}")
387                print("\n💡 Make sure the peer is running and the address is correct.")
388                return
389            else:
390                logger.error(f"Error during dialer operation: {error_msg}")
391                print(f"\nError during dialer operation: {error_msg}")
392                raise
393
394
395def main() -> None:
396    """Parse arguments and start the appropriate mode."""
397    description = """
398    This program demonstrates the libp2p identify/push protocol.
399    Without arguments, it runs as a listener on random port.
400    With -d parameter, it runs as a dialer on random port.
401
402    Port 0 (default) means the OS will automatically assign an available port.
403    This prevents port conflicts when running multiple instances.
404
405    Use --raw-format to send raw protobuf messages (old format) instead of
406    length-prefixed protobuf messages (new format, default).
407    """
408
409    parser = argparse.ArgumentParser(description=description)
410    parser.add_argument(
411        "-p",
412        "--port",
413        default=0,
414        type=int,
415        help="source port number (0 = random available port)",
416    )
417    parser.add_argument(
418        "-d",
419        "--destination",
420        type=str,
421        help="destination multiaddr string",
422    )
423    parser.add_argument(
424        "--raw-format",
425        action="store_true",
426        help=(
427            "use raw protobuf format (old format) instead of "
428            "length-prefixed (new format)"
429        ),
430    )
431
432    args = parser.parse_args()
433
434    # Determine format: raw format if --raw-format is specified, otherwise
435    # length-prefixed
436    use_varint_format = not args.raw_format
437
438    try:
439        if args.destination:
440            # Run in dialer mode with random available port if not specified
441            trio.run(run_dialer, args.port, args.destination, use_varint_format)
442        else:
443            # Run in listener mode with random available port if not specified
444            trio.run(run_listener, args.port, use_varint_format, args.raw_format)
445    except KeyboardInterrupt:
446        print("\n👋 Goodbye!")
447        logger.info("Application interrupted by user")
448    except Exception as e:
449        print(f"\n❌ Error: {str(e)}")
450        logger.error("Error: %s", str(e))
451        sys.exit(1)
452
453
454if __name__ == "__main__":
455    main()