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()