use crate::{
log,
network_service::{self, BitswapEvent, PeerId, SendBitswapMessageError},
platform::PlatformRef,
util,
};
use alloc::{
borrow::ToOwned,
boxed::Box,
collections::{BTreeSet, VecDeque},
format,
string::{String, ToString as _},
sync::Arc,
vec::Vec,
};
use core::{iter, pin::Pin, str::FromStr, time::Duration};
use futures_channel::oneshot;
use futures_lite::FutureExt as _;
use futures_util::{StreamExt as _, future, stream::FuturesUnordered};
use itertools::Itertools;
use rand::RngCore;
use rand_chacha::rand_core::SeedableRng as _;
use smoldot::{
json_rpc::parse,
libp2p::cid::{self, Cid, CidPrefix},
network::codec::{
Block, BlockPresence, BlockPresenceType, WantType, build_bitswap_cancel_message,
build_bitswap_message,
},
};
const PARALLEL_REQUESTS: usize = 50;
pub const MAX_CIDS_PER_REQUEST: usize = 64;
pub struct Config<TPlat: PlatformRef> {
pub log_name: String,
pub platform: TPlat,
pub network_service: Arc<network_service::NetworkServiceChain<TPlat>>,
}
pub struct BitswapService {
messages_tx: async_channel::Sender<ToBackground>,
}
impl BitswapService {
pub fn new<TPlat: PlatformRef>(
Config {
log_name,
platform,
network_service,
}: Config<TPlat>,
) -> Self {
let (messages_tx, messages_rx) = async_channel::bounded(32);
let log_target = format!("bitswap-service-{}", log_name);
let task = Box::pin(background_task(BackgroundTask {
log_target: log_target.clone(),
messages_rx: Box::pin(messages_rx),
network_service,
from_network_service: None,
pending_have_broadcast: None,
pending_block_requests: FuturesUnordered::new(),
platform: platform.clone(),
next_request_id_inner: 0,
next_batch_id_inner: 0,
randomness: rand_chacha::ChaCha20Rng::from_seed({
let mut seed = [0; 32];
platform.fill_random_bytes(&mut seed);
seed
}),
requests: hashbrown::HashMap::with_capacity_and_hasher(
PARALLEL_REQUESTS,
fnv::FnvBuildHasher::default(),
),
requests_by_timeout: BTreeSet::new(),
requests_by_cid: hashbrown::HashMap::with_capacity_and_hasher(
PARALLEL_REQUESTS,
util::SipHasherBuild::new({
let mut seed = [0; 16];
platform.fill_random_bytes(&mut seed);
seed
}),
),
batches: hashbrown::HashMap::with_capacity_and_hasher(
PARALLEL_REQUESTS,
fnv::FnvBuildHasher::default(),
),
}));
platform.spawn_task(log_target.clone().into(), {
let platform = platform.clone();
async move {
task.await;
log!(&platform, Debug, &log_target, "shutdown");
}
});
BitswapService { messages_tx }
}
pub async fn bitswap_get(&self, cid: String) -> Result<Vec<u8>, BitswapGetError> {
let cid = Cid::from_str(&cid).map_err(BitswapGetError::InvalidCid)?;
let (result_tx, result_rx) = oneshot::channel();
self.messages_tx
.send(ToBackground::BitswapBlock { cid, result_tx })
.await
.unwrap();
result_rx.await.unwrap()
}
pub async fn bitswap_stream(
&self,
cids: Vec<String>,
) -> Result<BitswapStreamHandle, BitswapGetError> {
let entries = parse_and_dedup(cids)?;
let (events_tx, events_rx) = async_channel::bounded(MAX_CIDS_PER_REQUEST);
let (ready_tx, ready_rx) = oneshot::channel();
self.messages_tx
.send(ToBackground::BitswapBatch {
entries,
events_tx,
ready_tx,
})
.await
.unwrap();
let batch_id = ready_rx.await.unwrap()?;
Ok(BitswapStreamHandle {
events_rx,
_cancel_guard: BatchCancelGuard {
batch_id,
messages_tx: self.messages_tx.clone(),
},
})
}
}
#[derive(Debug, Clone)]
pub enum BlockResult {
Ok(Vec<u8>),
Err(BitswapGetError),
}
pub struct BitswapStreamHandle {
pub events_rx: async_channel::Receiver<(String, BlockResult)>,
_cancel_guard: BatchCancelGuard,
}
struct BatchCancelGuard {
batch_id: BatchId,
messages_tx: async_channel::Sender<ToBackground>,
}
impl Drop for BatchCancelGuard {
fn drop(&mut self) {
let _ = self.messages_tx.try_send(ToBackground::CancelBatch {
batch_id: self.batch_id,
});
}
}
#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
pub enum BitswapGetError {
#[display("Invalid CID: {_0}")]
InvalidCid(cid::ParseError),
#[display("No Bitswap peers connected, can't issue \"have\" request.")]
NoPeers,
#[display("\"Block\" request to selected peer failed after successful \"have\" request.")]
BlockRequestFailed,
#[display("Network sending queue is full.")]
QueueFull,
#[display("No connected peers have the CID requested.")]
NotFound,
#[display("Request timeout.")]
Timeout,
#[display("Too many CIDs in batch request: max {max}, got {got}.")]
TooManyCids {
max: usize,
got: usize,
},
#[display("Input array is empty.")]
EmptyCids,
#[display("Input contains duplicate CIDs.")]
DuplicateCids,
}
enum BitswapJsonRpcError {
TooManyCids = -32801,
EmptyCids = -32802,
DuplicateCids = -32803,
Fail = -32810,
FailRetry = -32811,
FailRetryBackoff = -32812,
}
impl BitswapGetError {
pub fn to_json_rpc_error(&self, request_id_json: &str) -> String {
let message = self.to_string();
let error_response = match self {
BitswapGetError::InvalidCid(_) => parse::ErrorResponse::InvalidParams(Some(&message)),
BitswapGetError::NotFound => {
parse::ErrorResponse::ApplicationDefined(BitswapJsonRpcError::Fail as i64, &message)
}
BitswapGetError::BlockRequestFailed | BitswapGetError::Timeout => {
parse::ErrorResponse::ApplicationDefined(
BitswapJsonRpcError::FailRetry as i64,
&message,
)
}
BitswapGetError::QueueFull | BitswapGetError::NoPeers => {
parse::ErrorResponse::ApplicationDefined(
BitswapJsonRpcError::FailRetryBackoff as i64,
&message,
)
}
BitswapGetError::TooManyCids { .. } => parse::ErrorResponse::ApplicationDefined(
BitswapJsonRpcError::TooManyCids as i64,
&message,
),
BitswapGetError::EmptyCids => parse::ErrorResponse::ApplicationDefined(
BitswapJsonRpcError::EmptyCids as i64,
&message,
),
BitswapGetError::DuplicateCids => parse::ErrorResponse::ApplicationDefined(
BitswapJsonRpcError::DuplicateCids as i64,
&message,
),
};
parse::build_error_response(request_id_json, error_response, None)
}
pub fn to_block_result_err(&self) -> (i32, String) {
const INVALID_PARAMS: i32 = -32602;
let code = match self {
BitswapGetError::InvalidCid(_) => INVALID_PARAMS,
BitswapGetError::NotFound => BitswapJsonRpcError::Fail as i32,
BitswapGetError::BlockRequestFailed | BitswapGetError::Timeout => {
BitswapJsonRpcError::FailRetry as i32
}
BitswapGetError::QueueFull | BitswapGetError::NoPeers => {
BitswapJsonRpcError::FailRetryBackoff as i32
}
BitswapGetError::TooManyCids { .. }
| BitswapGetError::EmptyCids
| BitswapGetError::DuplicateCids => {
unreachable!("top-level batch-validation error, never emitted per-CID")
}
};
(code, self.to_string())
}
}
impl From<SendBitswapMessageError> for BitswapGetError {
fn from(error: SendBitswapMessageError) -> BitswapGetError {
match error {
SendBitswapMessageError::NoConnection => BitswapGetError::NoPeers,
SendBitswapMessageError::QueueFull => BitswapGetError::QueueFull,
}
}
}
pub fn parse_and_dedup(
cids: Vec<String>,
) -> Result<Vec<(String, Result<Cid, cid::ParseError>)>, BitswapGetError> {
if cids.is_empty() {
return Err(BitswapGetError::EmptyCids);
}
if cids.len() > MAX_CIDS_PER_REQUEST {
return Err(BitswapGetError::TooManyCids {
max: MAX_CIDS_PER_REQUEST,
got: cids.len(),
});
}
let mut seen_strings: hashbrown::HashSet<String> =
hashbrown::HashSet::with_capacity(cids.len());
let mut seen_digests: hashbrown::HashSet<[u8; 32]> =
hashbrown::HashSet::with_capacity(cids.len());
let mut out = Vec::with_capacity(cids.len());
for cid_str in cids {
if !seen_strings.insert(cid_str.clone()) {
return Err(BitswapGetError::DuplicateCids);
}
let parsed = Cid::from_str(&cid_str);
if let Ok(c) = &parsed {
if !seen_digests.insert(*c.digest()) {
return Err(BitswapGetError::DuplicateCids);
}
}
out.push((cid_str, parsed));
}
Ok(out)
}
enum ToBackground {
BitswapBlock {
cid: Cid,
result_tx: oneshot::Sender<Result<Vec<u8>, BitswapGetError>>,
},
BitswapBatch {
entries: Vec<(String, Result<Cid, cid::ParseError>)>,
events_tx: async_channel::Sender<(String, BlockResult)>,
ready_tx: oneshot::Sender<Result<BatchId, BitswapGetError>>,
},
CancelBatch { batch_id: BatchId },
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct RequestId(u64);
impl RequestId {
const _MIN: RequestId = RequestId(u64::MIN);
const MAX: RequestId = RequestId(u64::MAX);
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct BatchId(u64);
#[derive(Debug)]
enum RequestStage {
Have(hashbrown::HashSet<PeerId, util::SipHasherBuild>),
Block,
}
#[derive(Debug)]
enum SlotOutput {
Single(oneshot::Sender<Result<Vec<u8>, BitswapGetError>>),
Batch { batch_id: BatchId, slot_idx: usize },
}
#[derive(Debug)]
struct Request<TPlat: PlatformRef> {
result_tx: SlotOutput,
timeout: TPlat::Instant,
stage: RequestStage,
cid: Cid,
}
struct Batch {
events_tx: async_channel::Sender<(String, BlockResult)>,
cid_strs: Vec<String>,
slots: Vec<Option<RequestId>>,
peers_for_cancel: Vec<PeerId>,
pending_count: usize,
}
enum HaveContext {
Single {
cid: Cid,
result_tx: oneshot::Sender<Result<Vec<u8>, BitswapGetError>>,
},
Batch {
batch_id: BatchId,
cids: Vec<(usize, Cid)>,
ready_tx: oneshot::Sender<Result<BatchId, BitswapGetError>>,
},
}
type HaveBroadcastResult = (Result<Vec<PeerId>, SendBitswapMessageError>, HaveContext);
struct BackgroundTask<TPlat: PlatformRef> {
log_target: String,
messages_rx: Pin<Box<async_channel::Receiver<ToBackground>>>,
network_service: Arc<network_service::NetworkServiceChain<TPlat>>,
from_network_service: Option<Pin<Box<async_channel::Receiver<network_service::BitswapEvent>>>>,
pending_have_broadcast:
Option<Pin<Box<dyn Future<Output = HaveBroadcastResult> + Send + Sync>>>,
pending_block_requests: FuturesUnordered<
Pin<Box<dyn Future<Output = (Result<(), SendBitswapMessageError>, Cid)> + Send + Sync>>,
>,
platform: TPlat,
next_request_id_inner: u64,
next_batch_id_inner: u64,
randomness: rand_chacha::ChaCha20Rng,
requests: hashbrown::HashMap<RequestId, Request<TPlat>, fnv::FnvBuildHasher>,
requests_by_timeout: BTreeSet<(TPlat::Instant, RequestId)>,
requests_by_cid: hashbrown::HashMap<Cid, VecDeque<RequestId>, util::SipHasherBuild>,
batches: hashbrown::HashMap<BatchId, Batch, fnv::FnvBuildHasher>,
}
impl<TPlat: PlatformRef> BackgroundTask<TPlat> {
fn allocate_request_id(&mut self) -> RequestId {
let request_id = RequestId(self.next_request_id_inner);
self.next_request_id_inner += 1;
request_id
}
fn allocate_batch_id(&mut self) -> BatchId {
let batch_id = BatchId(self.next_batch_id_inner);
self.next_batch_id_inner += 1;
batch_id
}
fn deliver_slot(&mut self, slot_output: SlotOutput, result: Result<Vec<u8>, BitswapGetError>) {
match slot_output {
SlotOutput::Single(tx) => {
let _ = tx.send(result);
}
SlotOutput::Batch { batch_id, slot_idx } => {
self.deliver_batch_slot(batch_id, slot_idx, result);
}
}
}
fn deliver_batch_slot(
&mut self,
batch_id: BatchId,
slot_idx: usize,
result: Result<Vec<u8>, BitswapGetError>,
) {
let block_result = match result {
Ok(data) => BlockResult::Ok(data),
Err(e) => BlockResult::Err(e),
};
let (should_cancel, should_finalize) = {
let Some(batch) = self.batches.get_mut(&batch_id) else {
return;
};
batch.slots[slot_idx] = None;
batch.pending_count = batch.pending_count.saturating_sub(1);
let cid_str = batch.cid_strs[slot_idx].clone();
let mut should_cancel = false;
match batch.events_tx.try_send((cid_str, block_result)) {
Ok(()) => {}
Err(async_channel::TrySendError::Closed(_)) => {
should_cancel = true;
}
Err(async_channel::TrySendError::Full(_)) => {
log!(
&self.platform,
Error,
&self.log_target,
"BUG: stream events channel full — invariant violated"
);
unreachable!()
}
}
let should_finalize = !should_cancel && batch.pending_count == 0;
(should_cancel, should_finalize)
};
if should_cancel {
self.cancel_batch(batch_id);
} else if should_finalize {
self.finalize_batch(batch_id);
}
}
fn finalize_batch(&mut self, batch_id: BatchId) {
let Some(batch) = self.batches.remove(&batch_id) else {
return;
};
debug_assert_eq!(batch.pending_count, 0);
log!(
&self.platform,
Trace,
&self.log_target,
"batch finalized",
batch_id = batch_id.0,
slots = batch.cid_strs.len()
);
drop(batch.events_tx);
}
fn cancel_batch(&mut self, batch_id: BatchId) {
let Some(batch) = self.batches.remove(&batch_id) else {
return;
};
let mut pending_cids: Vec<Cid> = Vec::new();
for slot in batch.slots.into_iter() {
let Some(request_id) = slot else { continue };
let Some(request) = self.requests.remove(&request_id) else {
continue;
};
let _ = self
.requests_by_timeout
.remove(&(request.timeout, request_id));
if let hashbrown::hash_map::Entry::Occupied(mut entry) =
self.requests_by_cid.entry(request.cid.clone())
{
entry.get_mut().retain(|id| *id != request_id);
if entry.get().is_empty() {
entry.remove();
pending_cids.push(request.cid);
}
}
}
if pending_cids.is_empty() || batch.peers_for_cancel.is_empty() {
log!(
&self.platform,
Trace,
&self.log_target,
"batch cancelled (nothing to cancel on wire)",
batch_id = batch_id.0,
pending_cids = pending_cids.len(),
peers = batch.peers_for_cancel.len()
);
return;
}
log!(
&self.platform,
Trace,
&self.log_target,
"sending cancel wantlist",
batch_id = batch_id.0,
cids = ?pending_cids,
peers = batch.peers_for_cancel.len()
);
let message = build_bitswap_cancel_message(pending_cids.iter());
for peer in batch.peers_for_cancel {
let network_service = self.network_service.clone();
let msg = message.clone();
self.platform
.spawn_task(self.log_target.clone().into(), async move {
let _ = network_service.send_bitswap_message(peer, msg).await;
});
}
}
}
fn bitswap_have_message(cid: &Cid) -> Vec<u8> {
build_bitswap_message(iter::once(cid), WantType::Have, true, false)
}
fn bitswap_block_message(cid: &Cid) -> Vec<u8> {
build_bitswap_message(iter::once(cid), WantType::Block, false, false)
}
async fn background_task<TPlat: PlatformRef>(mut task: BackgroundTask<TPlat>) {
loop {
futures_lite::future::yield_now().await;
enum WakeUpReason {
MustSubscribeNetworkEvents,
NetworkEvent(network_service::BitswapEvent),
Message(ToBackground),
HaveBroadcastResult(HaveBroadcastResult),
BlockRequestResult((Result<(), SendBitswapMessageError>, Cid)),
RequestTimeout,
ForegroundClosed,
}
let wake_up_reason = {
let backpressure_messages = task.pending_have_broadcast.is_some();
async {
if let Some(from_network_service) = task.from_network_service.as_mut() {
match from_network_service.next().await {
Some(ev) => WakeUpReason::NetworkEvent(ev),
None => {
task.from_network_service = None;
WakeUpReason::MustSubscribeNetworkEvents
}
}
} else {
WakeUpReason::MustSubscribeNetworkEvents
}
}
.or(async {
if !backpressure_messages {
task.messages_rx
.next()
.await
.map_or(WakeUpReason::ForegroundClosed, WakeUpReason::Message)
} else {
future::pending().await
}
})
.or(async {
if let Some(pending_have_broadcast) = &mut task.pending_have_broadcast {
let result = pending_have_broadcast.await;
task.pending_have_broadcast = None;
WakeUpReason::HaveBroadcastResult(result)
} else {
future::pending().await
}
})
.or(async {
if !task.pending_block_requests.is_empty() {
let result = task
.pending_block_requests
.next()
.await
.expect("non-empty; qed");
WakeUpReason::BlockRequestResult(result)
} else {
future::pending().await
}
})
.or(async {
if let Some((first_timeout, _request_id)) = task.requests_by_timeout.first() {
let now = task.platform.now();
if now < *first_timeout {
task.platform.sleep(first_timeout.clone() - now).await;
}
WakeUpReason::RequestTimeout
} else {
future::pending().await
}
})
.await
};
match wake_up_reason {
WakeUpReason::MustSubscribeNetworkEvents => {
debug_assert!(task.from_network_service.is_none());
task.from_network_service = Some(Box::pin(
task.network_service.subscribe_bitswap().await,
));
}
WakeUpReason::Message(ToBackground::BitswapBlock { cid, result_tx }) => {
debug_assert!(task.pending_have_broadcast.is_none());
log!(
&task.platform,
Trace,
&task.log_target,
"queueing have wantlist (single)",
cid
);
let message = bitswap_have_message(&cid);
let network_service = task.network_service.clone();
task.pending_have_broadcast = Some(Box::pin(async move {
let result = network_service.broadcast_bitswap_message(message).await;
(result, HaveContext::Single { cid, result_tx })
}));
}
WakeUpReason::Message(ToBackground::BitswapBatch {
entries,
events_tx,
ready_tx,
}) => {
debug_assert!(task.pending_have_broadcast.is_none());
let batch_id = task.allocate_batch_id();
let total = entries.len();
let mut cid_strs: Vec<String> = Vec::with_capacity(total);
let mut slots: Vec<Option<RequestId>> = Vec::with_capacity(total);
let mut valid_cids: Vec<(usize, Cid)> = Vec::with_capacity(total);
let mut invalid_slots: Vec<(String, BitswapGetError)> = Vec::new();
for (slot_idx, (cid_str, parsed)) in entries.into_iter().enumerate() {
cid_strs.push(cid_str.clone());
slots.push(None);
match parsed {
Ok(c) => valid_cids.push((slot_idx, c)),
Err(e) => {
invalid_slots.push((cid_str, BitswapGetError::InvalidCid(e)));
}
}
}
let mut batch = Batch {
events_tx,
cid_strs,
slots,
peers_for_cancel: Vec::new(),
pending_count: total,
};
for (cid_str, err) in invalid_slots {
batch.pending_count = batch.pending_count.saturating_sub(1);
batch
.events_tx
.try_send((cid_str, BlockResult::Err(err)))
.unwrap_or_else(|_| unreachable!());
}
log!(
&task.platform,
Trace,
&task.log_target,
"batch queued",
batch_id = batch_id.0,
total,
valid = valid_cids.len(),
invalid = total - valid_cids.len()
);
task.batches.insert(batch_id, batch);
if valid_cids.is_empty() {
let _ = ready_tx.send(Ok(batch_id));
if task
.batches
.get(&batch_id)
.map_or(true, |b| b.pending_count == 0)
{
task.finalize_batch(batch_id);
}
continue;
}
log!(
&task.platform,
Trace,
&task.log_target,
"queueing have wantlist (batch)",
batch_id = batch_id.0,
cids = ?valid_cids.iter().map(|(_, c)| c).collect::<Vec<_>>()
);
let message = build_bitswap_message(
valid_cids.iter().map(|(_, c)| c),
WantType::Have,
true,
false,
);
let network_service = task.network_service.clone();
task.pending_have_broadcast = Some(Box::pin(async move {
let result = network_service.broadcast_bitswap_message(message).await;
(
result,
HaveContext::Batch {
batch_id,
cids: valid_cids,
ready_tx,
},
)
}));
}
WakeUpReason::Message(ToBackground::CancelBatch { batch_id }) => {
log!(
&task.platform,
Trace,
&task.log_target,
"cancel requested",
batch_id = batch_id.0
);
task.cancel_batch(batch_id);
}
WakeUpReason::HaveBroadcastResult((result, ctx)) => match ctx {
HaveContext::Single { cid, result_tx } => {
let broadcast_to = match result {
Ok(peers) => peers,
Err(err) => {
log!(
&task.platform,
Trace,
&task.log_target,
"have broadcast failed (single)",
cid,
?err
);
let _ = result_tx.send(Err(err.into()));
continue;
}
};
log!(
&task.platform,
Trace,
&task.log_target,
"have broadcast sent (single)",
cid,
peers = broadcast_to.len()
);
let request_id = task.allocate_request_id();
let timeout = task.platform.now() + Duration::from_secs(10);
let have_peers = {
let mut have_peers = hashbrown::HashSet::with_capacity_and_hasher(
broadcast_to.len(),
util::SipHasherBuild::new({
let mut seed = [0; 16];
task.randomness.fill_bytes(&mut seed);
seed
}),
);
have_peers.extend(broadcast_to.into_iter());
have_peers
};
task.requests.insert(
request_id,
Request {
result_tx: SlotOutput::Single(result_tx),
timeout: timeout.clone(),
stage: RequestStage::Have(have_peers),
cid: cid.clone(),
},
);
task.requests_by_timeout.insert((timeout, request_id));
task.requests_by_cid
.entry(cid)
.or_default()
.push_back(request_id);
}
HaveContext::Batch {
batch_id,
cids,
ready_tx,
} => {
let broadcast_to = match result {
Ok(peers) => peers,
Err(err) => {
let err: BitswapGetError = err.into();
log!(
&task.platform,
Trace,
&task.log_target,
"have broadcast failed (batch), rejecting subscription",
batch_id = batch_id.0,
?err
);
task.batches.remove(&batch_id);
let _ = ready_tx.send(Err(err));
continue;
}
};
log!(
&task.platform,
Trace,
&task.log_target,
"have broadcast sent (batch)",
batch_id = batch_id.0,
cid_count = cids.len(),
peers = broadcast_to.len()
);
debug_assert!(task.batches.contains_key(&batch_id));
if let Some(batch) = task.batches.get_mut(&batch_id) {
batch.peers_for_cancel = broadcast_to.clone();
} else {
continue;
}
let timeout = task.platform.now() + Duration::from_secs(10);
let have_peers_seed = {
let mut seed = [0; 16];
task.randomness.fill_bytes(&mut seed);
seed
};
for (slot_idx, cid) in cids {
let request_id = task.allocate_request_id();
let have_peers = {
let mut have_peers = hashbrown::HashSet::with_capacity_and_hasher(
broadcast_to.len(),
util::SipHasherBuild::new(have_peers_seed),
);
have_peers.extend(broadcast_to.iter().cloned());
have_peers
};
task.requests.insert(
request_id,
Request {
result_tx: SlotOutput::Batch { batch_id, slot_idx },
timeout: timeout.clone(),
stage: RequestStage::Have(have_peers),
cid: cid.clone(),
},
);
task.requests_by_timeout
.insert((timeout.clone(), request_id));
task.requests_by_cid
.entry(cid)
.or_default()
.push_back(request_id);
if let Some(batch) = task.batches.get_mut(&batch_id) {
batch.slots[slot_idx] = Some(request_id);
}
}
let _ = ready_tx.send(Ok(batch_id));
}
},
WakeUpReason::NetworkEvent(BitswapEvent::BitswapMessage { peer_id, message }) => {
let message = message.decode();
let mut deliveries: Vec<(SlotOutput, Result<Vec<u8>, BitswapGetError>)> =
Vec::new();
for BlockPresence { cid, presence_type } in message.block_presences {
let cid = match Cid::from_bytes(cid.to_owned()) {
Ok(cid) => cid,
Err(error) => {
log!(
&task.platform,
Debug,
&task.log_target,
"error decoding CID",
peer_id,
error,
);
continue;
}
};
let hashbrown::hash_map::Entry::Occupied(mut entry) =
task.requests_by_cid.entry(cid.clone())
else {
log!(
&task.platform,
Trace,
&task.log_target,
"stale/unsolicited have response",
peer_id
);
continue;
};
log!(
&task.platform,
Trace,
&task.log_target,
"presence response",
peer_id,
cid,
presence = match presence_type {
BlockPresenceType::Have => "have",
BlockPresenceType::DontHave => "dont_have",
}
);
let mut needs_block_request = false;
let request_ids = entry.get_mut();
for i in (0..request_ids.len()).rev() {
let request_id = request_ids[i];
let request = task.requests.get_mut(&request_id).unwrap();
match (&mut request.stage, presence_type) {
(RequestStage::Have(peers), BlockPresenceType::Have) => {
if peers.contains(&peer_id) {
request.stage = RequestStage::Block;
needs_block_request = true;
}
}
(RequestStage::Have(peers), BlockPresenceType::DontHave) => {
let _ = peers.remove(&peer_id);
if peers.is_empty() {
request_ids.remove(i);
let request = task.requests.remove(&request_id).unwrap();
let _was_in = task
.requests_by_timeout
.remove(&(request.timeout, request_id));
debug_assert!(_was_in);
log!(
&task.platform,
Trace,
&task.log_target,
"all peers dont have, NotFound",
cid = request.cid
);
deliveries
.push((request.result_tx, Err(BitswapGetError::NotFound)));
}
}
(RequestStage::Block, _) => {}
}
}
if entry.get().is_empty() {
entry.remove();
}
if needs_block_request {
log!(
&task.platform,
Trace,
&task.log_target,
"sending block request",
peer_id,
cid
);
let message = bitswap_block_message(&cid);
let network_service = task.network_service.clone();
let peer_id = peer_id.clone();
task.pending_block_requests.push(Box::pin(async move {
let result =
network_service.send_bitswap_message(peer_id, message).await;
(result, cid)
}));
}
}
for Block { prefix, data } in message.payload {
let prefix = match CidPrefix::from_bytes(prefix.to_owned()) {
Ok(prefix) => prefix,
Err(error) => {
log!(
&task.platform,
Debug,
&task.log_target,
"error decoding CID prefix",
peer_id,
error,
);
continue;
}
};
let cid = prefix.with_digest_of(data);
log!(
&task.platform,
Trace,
&task.log_target,
"block payload received",
peer_id,
cid,
bytes = data.len()
);
if let Some(request_ids) = task.requests_by_cid.remove(&cid) {
for request_id in request_ids {
let request = task.requests.remove(&request_id).unwrap();
let _was_in = task
.requests_by_timeout
.remove(&(request.timeout, request_id));
debug_assert!(_was_in);
task.deliver_slot(request.result_tx, Ok(data.to_owned()));
}
}
}
for (slot_output, result) in deliveries {
task.deliver_slot(slot_output, result);
}
}
WakeUpReason::BlockRequestResult((result, cid)) => {
if let Err(err) = result {
log!(
&task.platform,
Trace,
&task.log_target,
"block request failed",
cid,
?err
);
if let Some(request_ids) = task.requests_by_cid.remove(&cid) {
let err = match err {
SendBitswapMessageError::QueueFull => BitswapGetError::QueueFull,
SendBitswapMessageError::NoConnection => {
BitswapGetError::BlockRequestFailed
}
};
for request_id in request_ids {
let request = task.requests.remove(&request_id).unwrap();
let _was_in = task
.requests_by_timeout
.remove(&(request.timeout, request_id));
debug_assert!(_was_in);
task.deliver_slot(request.result_tx, Err(err.clone()));
}
}
}
}
WakeUpReason::RequestTimeout => {
let now = task.platform.now();
let requests = task
.requests_by_timeout
.range(..=(now, RequestId::MAX))
.cloned()
.collect::<Vec<_>>();
for (timeout, request_id) in requests {
task.requests_by_timeout.remove(&(timeout, request_id));
let request = task.requests.remove(&request_id).unwrap();
let cid = request.cid.clone();
match task.requests_by_cid.entry(request.cid) {
hashbrown::hash_map::Entry::Occupied(mut entry) => {
let (index, _) = entry
.get()
.iter()
.find_position(|id| **id == request_id)
.unwrap();
entry.get_mut().remove(index);
if entry.get().is_empty() {
entry.remove();
}
}
hashbrown::hash_map::Entry::Vacant(_) => unreachable!(),
}
log!(
&task.platform,
Trace,
&task.log_target,
"request timeout",
cid
);
task.deliver_slot(request.result_tx, Err(BitswapGetError::Timeout));
}
}
WakeUpReason::ForegroundClosed => {
return;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn extract_error_code(json: &str) -> i64 {
let parsed: serde_json::Value = serde_json::from_str(json).unwrap();
parsed["error"]["code"].as_i64().unwrap()
}
fn assert_no_error_data(json: &str) {
let parsed: serde_json::Value = serde_json::from_str(json).unwrap();
assert!(
parsed["error"].get("data").is_none(),
"error object must not carry `data`: {json}"
);
}
#[test]
fn error_invalid_cid_maps_to_invalid_params() {
let err = BitswapGetError::InvalidCid(Cid::from_str("not-a-cid").unwrap_err());
let json = err.to_json_rpc_error("\"1\"");
assert_eq!(extract_error_code(&json), -32602); assert_no_error_data(&json);
}
#[test]
fn error_not_found_maps_to_fail() {
let json = BitswapGetError::NotFound.to_json_rpc_error("\"1\"");
assert_eq!(extract_error_code(&json), -32810); assert_no_error_data(&json);
}
#[test]
fn error_block_request_failed_maps_to_fail_retry() {
let json = BitswapGetError::BlockRequestFailed.to_json_rpc_error("\"1\"");
assert_eq!(extract_error_code(&json), -32811); assert_no_error_data(&json);
}
#[test]
fn error_timeout_maps_to_fail_retry() {
let json = BitswapGetError::Timeout.to_json_rpc_error("\"1\"");
assert_eq!(extract_error_code(&json), -32811); assert_no_error_data(&json);
}
#[test]
fn error_queue_full_maps_to_fail_retry_backoff() {
let json = BitswapGetError::QueueFull.to_json_rpc_error("\"1\"");
assert_eq!(extract_error_code(&json), -32812); assert_no_error_data(&json);
}
#[test]
fn error_no_peers_maps_to_fail_retry_backoff() {
let json = BitswapGetError::NoPeers.to_json_rpc_error("\"1\"");
assert_eq!(extract_error_code(&json), -32812); assert_no_error_data(&json);
}
#[test]
fn error_response_is_valid_jsonrpc() {
let json = BitswapGetError::NotFound.to_json_rpc_error("42");
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["jsonrpc"], "2.0");
assert_eq!(parsed["id"], 42);
assert!(parsed["error"]["message"].is_string());
}
#[test]
fn from_send_error_no_connection() {
let err: BitswapGetError = SendBitswapMessageError::NoConnection.into();
assert!(matches!(err, BitswapGetError::NoPeers));
}
#[test]
fn from_send_error_queue_full() {
let err: BitswapGetError = SendBitswapMessageError::QueueFull.into();
assert!(matches!(err, BitswapGetError::QueueFull));
}
#[test]
fn error_too_many_cids_maps_to_too_many_cids() {
let err = BitswapGetError::TooManyCids { max: 64, got: 100 };
let json = err.to_json_rpc_error("\"1\"");
assert_eq!(extract_error_code(&json), -32801); assert_no_error_data(&json);
}
#[test]
fn error_empty_cids_maps_to_empty_cids() {
let json = BitswapGetError::EmptyCids.to_json_rpc_error("\"1\"");
assert_eq!(extract_error_code(&json), -32802); assert_no_error_data(&json);
}
#[test]
fn error_duplicate_cids_maps_to_duplicate_cids() {
let json = BitswapGetError::DuplicateCids.to_json_rpc_error("\"1\"");
assert_eq!(extract_error_code(&json), -32803); assert_no_error_data(&json);
}
#[test]
fn block_result_err_codes_match_top_level_codes() {
assert_eq!(BitswapGetError::NotFound.to_block_result_err().0, -32810);
assert_eq!(BitswapGetError::Timeout.to_block_result_err().0, -32811);
assert_eq!(
BitswapGetError::BlockRequestFailed.to_block_result_err().0,
-32811
);
assert_eq!(BitswapGetError::QueueFull.to_block_result_err().0, -32812);
assert_eq!(BitswapGetError::NoPeers.to_block_result_err().0, -32812);
assert_eq!(
BitswapGetError::InvalidCid(Cid::from_str("not-a-cid").unwrap_err())
.to_block_result_err()
.0,
-32602
);
}
const VALID_CID_A: &str = "bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku";
const VALID_CID_B: &str = "bafkreigh2akiscaildc3rdvuwhszwgrtgvybsh7lhxavhgqitanwh4kc6q";
#[test]
fn parse_and_dedup_empty_input_is_rejected() {
let err = parse_and_dedup(vec![]).unwrap_err();
assert!(matches!(err, BitswapGetError::EmptyCids));
}
#[test]
fn parse_and_dedup_happy_path() {
let out = parse_and_dedup(vec![VALID_CID_A.into(), VALID_CID_B.into()]).unwrap();
assert_eq!(out.len(), 2);
assert_eq!(out[0].0, VALID_CID_A);
assert!(out[0].1.is_ok());
assert_eq!(out[1].0, VALID_CID_B);
assert!(out[1].1.is_ok());
}
#[test]
fn parse_and_dedup_preserves_invalid_inputs_per_slot() {
let out = parse_and_dedup(vec![
VALID_CID_A.into(),
"garbage".into(),
VALID_CID_B.into(),
])
.unwrap();
assert_eq!(out.len(), 3);
assert!(out[0].1.is_ok());
assert!(out[1].1.is_err());
assert!(out[2].1.is_ok());
}
#[test]
fn parse_and_dedup_rejects_literal_string_duplicate() {
let err = parse_and_dedup(vec!["garbage".into(), "garbage".into()]).unwrap_err();
assert!(matches!(err, BitswapGetError::DuplicateCids));
}
#[test]
fn parse_and_dedup_rejects_valid_string_duplicate() {
let err = parse_and_dedup(vec![VALID_CID_A.into(), VALID_CID_A.into()]).unwrap_err();
assert!(matches!(err, BitswapGetError::DuplicateCids));
}
#[test]
fn parse_and_dedup_rejects_too_many_cids() {
let cids = (0..MAX_CIDS_PER_REQUEST + 1)
.map(|_| VALID_CID_A.into())
.collect();
let err = parse_and_dedup(cids).unwrap_err();
assert!(matches!(
err,
BitswapGetError::TooManyCids { max: MAX_CIDS_PER_REQUEST, got } if got == MAX_CIDS_PER_REQUEST + 1
));
}
#[test]
fn parse_and_dedup_accepts_max_size() {
let cids: Vec<String> = (0..MAX_CIDS_PER_REQUEST)
.map(|i| format!("invalid-{i}"))
.collect();
let out = parse_and_dedup(cids).unwrap();
assert_eq!(out.len(), MAX_CIDS_PER_REQUEST);
}
}