HashPicker
Decides which BitTorrent v2 merkle-hash ranges to request from peers, and validates the hashes responses into per-file MerkleTrees. This is a practical-level port of libtorrent's hash_picker (src/hash_picker.cpp).
What this is for
A v2 download started from a magnet (or any time the .torrent's piece layers are absent) does not yet know each file's per-piece roots. Before a peer's piece data can be verified, the client must obtain the piece layer of every multi-piece file. Peers supply it through the hash_request / hashes / hash_reject exchange (BEP-52):
We send a PeerMessage.HashRequest naming the file (by its
pieces root), the layer we want (base), anindex/lengthwindow into that layer, and how manyproofLayers(uncle hashes) we need to anchor the response to the file root we already trust.The peer replies with PeerMessage.Hashes:
lengthhashes at the requested layer, followed by the uncle hashes. We fold them with Merkle.verifyProof against the file root; on success they go into that file's MerkleTree.Or the peer replies with PeerMessage.HashReject, freeing the slot to retry elsewhere.
Scope vs. libtorrent
libtorrent's production picker carries 512-piece request buckets, a future per-block priority-request path, and min_request_interval rate limiting. This port keeps the load-bearing core that matters for correctness:
per-file piece-layer requests anchored to the known file root,
proof verification via the Tier-A Merkle.verifyProof,
insertion of validated piece roots via MerkleTree.loadPieceLayerRow,
outstanding-request tracking so a piece is not requested twice concurrently,
reject handling that re-opens the slot.
It requests a file's piece layer in one window (libtorrent's common case for files of up to 512 pieces); larger files are still handled correctly, just in a single larger request.
The picker needs each file's own MerkleTree (constructed with the file's known root). It does not own them (the session does), so they are passed in keyed by file index.
Parameters
the parsed torrent (provides the file→piece mapping and known file roots).
per-file merkle trees, keyed by file index. A file's tree must have been constructed with that file's pieces root as its expected root for verification to work. Pad files and single-piece files may be omitted.
Functions
Builds the PeerMessage.HashRequest needed to obtain the piece-layer hashes for the file that owns piece, or null when no request is warranted (needHashes is false, or a request for this file is already outstanding). On success the file is marked as having an outstanding request until onHashes or onHashReject is called for it.
Whether a piece-layer request for the file owning piece is currently in flight.
True if we still need v2 hashes to verify piece. That happens when its owning file is a real (non-pad) multi-piece file whose piece-layer node for this piece is not yet present in its MerkleTree. Single-piece files need no request (the file root is the piece hash), and files whose piece layers were already loaded from the .torrent return false. Mirrors the negation of hash_picker::have_hash.
Validates and applies a received PeerMessage.Hashes. The message is matched to a file by its piecesRoot; its leading length hashes are the requested piece-layer run and any trailing hashes are uncle/proof hashes. The run + proof are folded with Merkle.verifyProof against the file's known root; on success the validated piece roots are written into that file's MerkleTree via MerkleTree.loadPieceLayerRow and the outstanding-request slot is cleared.
Handles a PeerMessage.HashReject: the peer declined our request for that file. Clears the outstanding slot so the file can be requested again (from this or another peer). Mirrors hash_picker::hashes_rejected (the bucket's request is re-opened). No-op if the file is unknown or had no outstanding request.
True once every (real) block of piece has been verified against its file's root.