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