use std::{
net::{Ipv4Addr, SocketAddr},
time::Duration,
};
use dig_peer_protocol::{
Bytes, DigLink, DigMessage, LinkError, LinkOptions, DIG_MESSAGE, HOLDINGS_ANNOUNCE,
STORE_MELTED,
};
use tokio::io::DuplexStream;
use tokio_tungstenite::{tungstenite::protocol::Role, WebSocketStream};
const PATIENCE: Duration = Duration::from_secs(5);
const REQUEST_TIMEOUT: Duration = Duration::from_millis(300);
async fn linked_pair() -> (
DigLink,
tokio::sync::mpsc::Receiver<DigMessage>,
DigLink,
tokio::sync::mpsc::Receiver<DigMessage>,
) {
let mut options = LinkOptions::default();
options.request_timeout = REQUEST_TIMEOUT;
let (left, right) = tokio::io::duplex(1024 * 1024);
let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, 8444));
let client: WebSocketStream<DuplexStream> =
WebSocketStream::from_raw_socket(left, Role::Client, None).await;
let server: WebSocketStream<DuplexStream> =
WebSocketStream::from_raw_socket(right, Role::Server, None).await;
let (a, a_rx) = DigLink::from_server_websocket(client, addr, options);
let (b, b_rx) = DigLink::from_server_websocket(server, addr, options);
(a, a_rx, b, b_rx)
}
#[tokio::test]
async fn a_junk_reply_reports_invalid_response_rather_than_a_bare_timeout() {
let (peer, mut peer_rx, requester, mut requester_rx) = linked_pair().await;
let peer_task = tokio::spawn(async move {
let ours = peer_rx.recv().await.expect("the peer receives our request");
peer.send_message(DigMessage::new(
DIG_MESSAGE,
ours.id,
Bytes::new(b"answers-nothing-we-asked".to_vec()),
))
.await
.expect("the peer sends the undeclared-opcode frame");
});
let outcome = tokio::time::timeout(
PATIENCE,
requester.request_dig(
HOLDINGS_ANNOUNCE,
&[STORE_MELTED],
Bytes::new(b"ping".to_vec()),
),
)
.await
.expect("the request hung past its deadline");
match outcome {
Ok(message) => panic!(
"an undeclared opcode ({}) was delivered as the answer",
message.msg_type
),
Err(LinkError::InvalidResponse(expected, found)) => {
assert_eq!(
expected,
vec![STORE_MELTED],
"the diagnostic must name the opcodes the request declared"
);
assert_eq!(
found, DIG_MESSAGE,
"the diagnostic must name the opcode that actually arrived"
);
}
Err(other) => panic!(
"expected InvalidResponse naming the junk opcode, got {other} — a caller cannot \
tell this peer apart from one that said nothing"
),
}
let delivered = tokio::time::timeout(PATIENCE, requester_rx.recv())
.await
.expect("the undeclared frame was never delivered to the application")
.expect("the inbound channel closed");
assert_eq!(delivered.msg_type, DIG_MESSAGE);
assert_eq!(delivered.data.as_ref(), b"answers-nothing-we-asked");
peer_task.await.expect("the peer finished");
}
#[tokio::test]
async fn a_silent_peer_still_reports_a_plain_timeout() {
let (_peer, _peer_rx, requester, _requester_rx) = linked_pair().await;
let outcome = tokio::time::timeout(
PATIENCE,
requester.request_dig(
HOLDINGS_ANNOUNCE,
&[STORE_MELTED],
Bytes::new(b"ping".to_vec()),
),
)
.await
.expect("the request hung past its deadline");
match outcome {
Err(LinkError::RequestTimeout(opcode)) => assert_eq!(
opcode, HOLDINGS_ANNOUNCE,
"the timeout must name the request's own opcode"
),
Err(other) => panic!("a silent peer must report RequestTimeout, got {other}"),
Ok(_) => panic!("a request nobody answered resolved successfully"),
}
}
#[tokio::test]
async fn request_infallible_rejects_a_body_typed_reply_it_did_not_ask_for() {
use chia_protocol::NewPeakWallet;
let (peer, mut peer_rx, requester, _requester_rx) = linked_pair().await;
let peer_task = tokio::spawn(async move {
let ours = peer_rx.recv().await.expect("the peer receives our request");
peer.send_message(DigMessage::new(
HOLDINGS_ANNOUNCE,
ours.id,
Bytes::new(b"not-a-new-peak-wallet".to_vec()),
))
.await
.expect("the peer sends the undeclared-opcode frame");
});
let request = NewPeakWallet::new(Default::default(), 0, Default::default(), 0);
let outcome = tokio::time::timeout(
PATIENCE,
requester.request_infallible::<NewPeakWallet, _>(request),
)
.await
.expect("the request hung past its deadline");
match outcome {
Err(LinkError::InvalidResponse(_, found)) => assert_eq!(
found, HOLDINGS_ANNOUNCE,
"the diagnostic must name the opcode that actually arrived"
),
Err(other) => panic!("expected InvalidResponse, got {other}"),
Ok(_) => panic!("a frame of the wrong opcode was parsed as the reply body"),
}
peer_task.await.expect("the peer finished");
}