I’m stuck on Stage #ND2.
I’ve followed all information in the hints and interestingly my code works locally when I try download any piece (0, 1, 2) from any server from the sample.torrent file via:
./your_bittorrent.sh download_piece -o test-piece-0 sample.torrent 0
I am able to successfully verify that the hashes of the downloaded piece match the associated piece hash in the torrent file.
However running the same code with codecrafters test, always results in the same error:
[tester::#ND2] Running tests for Stage #ND2 (Download a piece)
[tester::#ND2] Running ./your_bittorrent.sh download_piece -o /tmp/torrents2250797056/piece-2 /tmp/torrents2250797056/congratulations.gif.torrent 2
[your_program] Connecting to 161.35.46.221:51486
[your_program] Traceback (most recent call last):
[your_program] File "<frozen runpy>", line 198, in _run_module_as_main
[your_program] File "<frozen runpy>", line 88, in _run_code
[your_program] File "/app/app/main.py", line 330, in <module>
[your_program] main()
[your_program] File "/app/app/main.py", line 319, in main
[your_program] piece = download_piece(torrent_metainfo, piece_index)
[your_program] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[your_program] File "/app/app/main.py", line 230, in download_piece
[your_program] handshake_resp, s = peer_handshake(metainfo, peers[0])
[your_program] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[your_program] File "/app/app/main.py", line 213, in peer_handshake
[your_program] resp = s.recv(len(handshake))
[your_program] ^^^^^^^^^^^^^^^^^^^^^^
[your_program] ConnectionResetError: [Errno 104] Connection reset by peer
[tester::#ND2] Application didn't terminate successfully without errors. Expected 0 as exit code, got: 1
[tester::#ND2] Test failed
And here’s a snippet of my code:
def peer_handshake(
metainfo: TorrentMetainfo, addr: tuple[str, int]
) -> tuple[bytes, socket.socket]:
ip, port = addr
print(f"Connecting to {ip}:{port}")
handshake = (
(19).to_bytes(1)
+ b"BitTorrent protocol"
+ (0).to_bytes(8)
+ metainfo.info_hash
+ (112233445566778899).to_bytes(20)
)
s = socket.socket()
s.connect((ip, int(port)))
s.send(handshake)
resp = s.recv(len(handshake))
return resp, s
def recv_message(s: socket.socket) -> bytes:
msg_len = int.from_bytes(s.recv(4))
print(f"Received message of length {msg_len}")
msg = s.recv(msg_len)
while len(msg) < msg_len:
msg += s.recv(msg_len - len(msg))
return msg
def download_piece(metainfo: TorrentMetainfo, piece_idx: int):
peers = fetch_peers(metainfo)
handshake_resp, s = peer_handshake(metainfo, peers[0])
# Wait for bitfield message
msg = recv_message(s)
assert msg[0] == 5
# Send interested message
s.send((1).to_bytes(4) + (2).to_bytes(1))
# Wait for unchoke message
msg = recv_message(s)
assert msg[0] == 1
# Determine size of requested piece
piece_size = metainfo.piece_length
num_pieces = len(metainfo.piece_hashes)
if piece_idx == num_pieces - 1:
piece_size = metainfo.length - (piece_size * (num_pieces - 1))
# Determine number of blocks making up the piece
num_blocks = math.ceil(piece_size / STD_BLOCK_SIZE)
# Send request messages for the piece blocks
for b in range(num_blocks):
# Smaller block size for last block
block_size = STD_BLOCK_SIZE
if b == num_blocks - 1:
block_size = piece_size - (STD_BLOCK_SIZE * (num_blocks - 1))
payload = (
(piece_idx).to_bytes(4)
+ (b * STD_BLOCK_SIZE).to_bytes(4)
+ (block_size).to_bytes(4)
)
s.send((1 + len(payload)).to_bytes(4) + (6).to_bytes(1) + payload)
print(f"Sent request for piece {piece_idx} block {b}")
# Wait for piece messages
recv_blocks = []
while len(recv_blocks) < num_blocks:
msg = recv_message(s)
assert msg[0] == 7
p = int.from_bytes(msg[1:5])
begin = int.from_bytes(msg[5:9])
block = msg[9:]
print(f"Received piece {p} block {begin // STD_BLOCK_SIZE}")
recv_blocks.append((begin, block))
# Create piece from blocks
piece = b"".join([block for _, block in recv_blocks])
# Verify piece hash
assert hashlib.sha1(piece).digest() == metainfo.piece_hashes[piece_idx]
return piece
