Identify Protocol Demo

This example demonstrates how to use the libp2p identify protocol.

$ python -m pip install libp2p
Collecting libp2p
...
Successfully installed libp2p-x.x.x
$ identify-demo
First host listening. Run this from another console:

identify-demo -p 8889 -d /ip4/127.0.0.1/tcp/8888/p2p/QmUiN4R3fNrCoQugGgmmb3v35neMEjKFNrsbNGVDsRHWpM

Waiting for incoming identify request...

Copy the line that starts with identify-demo -p 8889 ...., open a new terminal in the same folder and paste it in:

$ identify-demo -p 8889 -d /ip4/127.0.0.1/tcp/8888/p2p/QmUiN4R3fNrCoQugGgmmb3v35neMEjKFNrsbNGVDsRHWpM
dialer (host_b) listening on /ip4/127.0.0.1/tcp/8889
Second host connecting to peer: QmUiN4R3fNrCoQugGgmmb3v35neMEjKFNrsbNGVDsRHWpM
Starting identify protocol...
Identify response:
Public Key (Base64): CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC6c/oNPP9X13NDQ3Xrlp3zOj+ErXIWb/A4JGwWchiDBwMhMslEX3ct8CqI0BqUYKuwdFjowqqopOJ3cS2MlqtGaiP6Dg9bvGqSDoD37BpNaRVNcebRxtB0nam9SQy3PYLbHAmz0vR4ToSiL9OLRORnGOxCtHBuR8ZZ5vS0JEni8eQMpNa7IuXwyStnuty/QjugOZudBNgYSr8+9gH722KTjput5IRL7BrpIdd4HNXGVRm4b9BjNowvHu404x3a/ifeNblpy/FbYyFJEW0looygKF7hpRHhRbRKIDZt2BqOfT1sFkbqsHE85oY859+VMzP61YELgvGwai2r7KcjkW/AgMBAAE=
Listen Addresses: ['/ip4/127.0.0.1/tcp/8888/p2p/QmUiN4R3fNrCoQugGgmmb3v35neMEjKFNrsbNGVDsRHWpM']
Protocols: ['/ipfs/id/1.0.0', '/ipfs/ping/1.0.0']
Observed Address: ['/ip4/127.0.0.1/tcp/38082']
Protocol Version: ipfs/0.1.0
Agent Version: py-libp2p/0.2.0

The full source code for this example is below:

  1import argparse
  2import base64
  3import logging
  4import sys
  5
  6import multiaddr
  7import trio
  8
  9from libp2p import (
 10    new_host,
 11)
 12from libp2p.identity.identify.identify import (
 13    ID as IDENTIFY_PROTOCOL_ID,
 14    identify_handler_for,
 15    parse_identify_response,
 16)
 17from libp2p.identity.identify.pb.identify_pb2 import Identify
 18from libp2p.peer.envelope import debug_dump_envelope, unmarshal_envelope
 19from libp2p.peer.peerinfo import (
 20    info_from_p2p_addr,
 21)
 22
 23# Configure minimal logging
 24logging.basicConfig(level=logging.WARNING)
 25logging.getLogger("multiaddr").setLevel(logging.WARNING)
 26logging.getLogger("libp2p").setLevel(logging.WARNING)
 27
 28logger = logging.getLogger("libp2p.identity.identify-example")
 29
 30
 31def decode_multiaddrs(raw_addrs):
 32    """Convert raw listen addresses into human-readable multiaddresses."""
 33    decoded_addrs = []
 34    for addr in raw_addrs:
 35        try:
 36            decoded_addrs.append(str(multiaddr.Multiaddr(addr)))
 37        except Exception as e:
 38            decoded_addrs.append(f"Invalid Multiaddr ({addr}): {e}")
 39    return decoded_addrs
 40
 41
 42def print_identify_response(identify_response: Identify):
 43    """Pretty-print Identify response."""
 44    public_key_b64 = base64.b64encode(identify_response.public_key).decode("utf-8")
 45    listen_addrs = decode_multiaddrs(identify_response.listen_addrs)
 46    signed_peer_record = unmarshal_envelope(identify_response.signedPeerRecord)
 47    try:
 48        observed_addr_decoded = decode_multiaddrs([identify_response.observed_addr])
 49    except Exception:
 50        observed_addr_decoded = identify_response.observed_addr
 51    print(
 52        f"Identify response:\n"
 53        f"  Public Key (Base64): {public_key_b64}\n"
 54        f"  Listen Addresses: {listen_addrs}\n"
 55        f"  Protocols: {list(identify_response.protocols)}\n"
 56        f"  Observed Address: "
 57        f"{observed_addr_decoded if identify_response.observed_addr else 'None'}\n"
 58        f"  Protocol Version: {identify_response.protocol_version}\n"
 59        f"  Agent Version: {identify_response.agent_version}"
 60    )
 61
 62    debug_dump_envelope(signed_peer_record)
 63
 64
 65async def run(port: int, destination: str, use_varint_format: bool = True) -> None:
 66    from libp2p.utils.address_validation import (
 67        get_available_interfaces,
 68        get_optimal_binding_address,
 69    )
 70
 71    if not destination:
 72        # Create first host (listener)
 73        if port <= 0:
 74            from libp2p.utils.address_validation import find_free_port
 75
 76            port = find_free_port()
 77
 78        listen_addrs = get_available_interfaces(port)
 79        host_a = new_host()
 80
 81        # Set up identify handler with specified format
 82        # Set use_varint_format = False, if want to checkout the Signed-PeerRecord
 83        identify_handler = identify_handler_for(
 84            host_a, use_varint_format=use_varint_format
 85        )
 86        host_a.set_stream_handler(IDENTIFY_PROTOCOL_ID, identify_handler)
 87
 88        async with (
 89            host_a.run(listen_addrs=listen_addrs),
 90            trio.open_nursery() as nursery,
 91        ):
 92            # Start the peer-store cleanup task
 93            nursery.start_soon(host_a.get_peerstore().start_cleanup_task, 60)
 94
 95            # Get all available addresses with peer ID
 96            all_addrs = host_a.get_addrs()
 97
 98            if use_varint_format:
 99                format_name = "length-prefixed"
100                print(f"First host listening (using {format_name} format).")
101                print("Listener ready, listening on:\n")
102                for addr in all_addrs:
103                    print(f"{addr}")
104
105                # Use optimal address for the client command
106                optimal_addr = get_optimal_binding_address(port)
107                optimal_addr_with_peer = (
108                    f"{optimal_addr}/p2p/{host_a.get_id().to_string()}"
109                )
110                print(
111                    f"\nRun this from the same folder in another console:\n\n"
112                    f"identify-demo -d {optimal_addr_with_peer}\n"
113                )
114                print("Waiting for incoming identify request...")
115            else:
116                format_name = "raw protobuf"
117                print(f"First host listening (using {format_name} format).")
118                print("Listener ready, listening on:\n")
119                for addr in all_addrs:
120                    print(f"{addr}")
121
122                # Use optimal address for the client command
123                optimal_addr = get_optimal_binding_address(port)
124                optimal_addr_with_peer = (
125                    f"{optimal_addr}/p2p/{host_a.get_id().to_string()}"
126                )
127                print(
128                    f"\nRun this from the same folder in another console:\n\n"
129                    f"identify-demo -d {optimal_addr_with_peer}\n"
130                )
131                print("Waiting for incoming identify request...")
132
133            # Add a custom handler to show connection events
134            async def custom_identify_handler(stream):
135                peer_id = stream.muxed_conn.peer_id
136                print(f"\n🔗 Received identify request from peer: {peer_id}")
137
138                # Show remote address in multiaddr format
139                try:
140                    from libp2p.identity.identify.identify import (
141                        _remote_address_to_multiaddr,
142                    )
143
144                    remote_address = stream.get_remote_address()
145                    if remote_address:
146                        observed_multiaddr = _remote_address_to_multiaddr(
147                            remote_address
148                        )
149                        # Add the peer ID to create a complete multiaddr
150                        complete_multiaddr = f"{observed_multiaddr}/p2p/{peer_id}"
151                        print(f"   Remote address: {complete_multiaddr}")
152                    else:
153                        print(f"   Remote address: {remote_address}")
154                except Exception:
155                    print(f"   Remote address: {stream.get_remote_address()}")
156
157                # Call the original handler
158                await identify_handler(stream)
159
160                print(f"✅ Successfully processed identify request from {peer_id}")
161
162            # Replace the handler with our custom one
163            host_a.set_stream_handler(IDENTIFY_PROTOCOL_ID, custom_identify_handler)
164
165            try:
166                await trio.sleep_forever()
167            except KeyboardInterrupt:
168                print("\n🛑 Shutting down listener...")
169                logger.info("Listener interrupted by user")
170                return
171
172    else:
173        # Create second host (dialer)
174        from libp2p.utils.address_validation import (
175            find_free_port,
176            get_available_interfaces,
177            get_optimal_binding_address,
178        )
179
180        if port <= 0:
181            port = find_free_port()
182
183        listen_addrs = get_available_interfaces(port)
184        host_b = new_host()
185
186        async with (
187            host_b.run(listen_addrs=listen_addrs),
188            trio.open_nursery() as nursery,
189        ):
190            # Start the peer-store cleanup task
191            nursery.start_soon(host_b.get_peerstore().start_cleanup_task, 60)
192
193            # Connect to the first host
194            print(f"dialer (host_b) listening on {host_b.get_addrs()[0]}")
195            maddr = multiaddr.Multiaddr(destination)
196            info = info_from_p2p_addr(maddr)
197            print(f"Second host connecting to peer: {info.peer_id}")
198
199            try:
200                await host_b.connect(info)
201            except Exception as e:
202                error_msg = str(e)
203                if "unable to connect" in error_msg or "SwarmException" in error_msg:
204                    print(f"\n❌ Cannot connect to peer: {info.peer_id}")
205                    print(f"   Address: {destination}")
206                    print(f"   Error: {error_msg}")
207                    print(
208                        "\n💡 Make sure the peer is running and the address is correct."
209                    )
210                    return
211                else:
212                    # Re-raise other exceptions
213                    raise
214
215            stream = await host_b.new_stream(info.peer_id, (IDENTIFY_PROTOCOL_ID,))
216
217            try:
218                print("Starting identify protocol...")
219
220                # Read the response using the utility function
221                from libp2p.utils.varint import read_length_prefixed_protobuf
222
223                response = await read_length_prefixed_protobuf(
224                    stream, use_varint_format
225                )
226                full_response = response
227
228                await stream.close()
229
230                # Parse the response using the robust protocol-level function
231                # This handles both old and new formats automatically
232                identify_msg = parse_identify_response(full_response)
233                print_identify_response(identify_msg)
234
235            except Exception as e:
236                error_msg = str(e)
237                print(f"Identify protocol error: {error_msg}")
238
239                # Check for specific format mismatch errors
240                if "Error parsing message" in error_msg or "DecodeError" in error_msg:
241                    print("\n" + "=" * 60)
242                    print("FORMAT MISMATCH DETECTED!")
243                    print("=" * 60)
244                    if use_varint_format:
245                        print(
246                            "You are using length-prefixed format (default) but the "
247                            "listener"
248                        )
249                        print("is using raw protobuf format.")
250                        print(
251                            "\nTo fix this, run the dialer with the --raw-format flag:"
252                        )
253                        print(f"identify-demo --raw-format -d {destination}")
254                    else:
255                        print("You are using raw protobuf format but the listener")
256                        print("is using length-prefixed format (default).")
257                        print(
258                            "\nTo fix this, run the dialer without the --raw-format "
259                            "flag:"
260                        )
261                        print(f"identify-demo -d {destination}")
262                    print("=" * 60)
263                else:
264                    import traceback
265
266                    traceback.print_exc()
267
268            return
269
270
271def main() -> None:
272    description = """
273    This program demonstrates the libp2p identify protocol.
274    First run 'identify-demo -p <PORT> [--raw-format]' to start a listener.
275    Then run 'identify-demo <ANOTHER_PORT> -d <DESTINATION>'
276    where <DESTINATION> is the multiaddress shown by the listener.
277
278    Use --raw-format to send raw protobuf messages (old format) instead of
279    length-prefixed protobuf messages (new format, default).
280    """
281
282    example_maddr = (
283        "/ip4/[HOST_IP]/tcp/8888/p2p/QmQn4SwGkDZKkUEpBRBvTmheQycxAHJUNmVEnjA2v1qe8Q"
284    )
285
286    parser = argparse.ArgumentParser(description=description)
287    parser.add_argument("-p", "--port", default=0, type=int, help="source port number")
288    parser.add_argument(
289        "-d",
290        "--destination",
291        type=str,
292        help=f"destination multiaddr string, e.g. {example_maddr}",
293    )
294    parser.add_argument(
295        "--raw-format",
296        action="store_true",
297        help=(
298            "use raw protobuf format (old format) instead of "
299            "length-prefixed (new format)"
300        ),
301    )
302
303    args = parser.parse_args()
304
305    # Determine format: use varint (length-prefixed) if --raw-format is specified,
306    # otherwise use raw protobuf format (old format)
307    use_varint_format = not args.raw_format
308
309    try:
310        if args.destination:
311            # Run in dialer mode
312            trio.run(run, *(args.port, args.destination, use_varint_format))
313        else:
314            # Run in listener mode
315            trio.run(run, *(args.port, args.destination, use_varint_format))
316    except KeyboardInterrupt:
317        print("\n👋 Goodbye!")
318        logger.info("Application interrupted by user")
319    except Exception as e:
320        print(f"\n❌ Error: {str(e)}")
321        logger.error("Error: %s", str(e))
322        sys.exit(1)
323
324
325if __name__ == "__main__":
326    main()