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
            