Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fuel/Request_Response v0.0.2: More meaningful error messages #2377

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ab26a0b
Add a module for versioning the request response protocols in the p2p…
acerone85 Oct 15, 2024
c9ba074
Record the latest compatible version for the request response protoco…
acerone85 Oct 15, 2024
b3d3219
P2PService uses peer manager information to determine the version of …
acerone85 Oct 15, 2024
8817d88
Remove argument from Protocol::V1 and minor improvements to tests
acerone85 Oct 16, 2024
2ad29e4
Merge branch 'master' into 1311-return-better-error-messages-from-p2p…
acerone85 Oct 16, 2024
aedda11
Merge branch 'master' into 1311-return-better-error-messages-from-p2p…
acerone85 Oct 16, 2024
1855350
Add changelog entry
acerone85 Oct 16, 2024
1fa98a4
Fix compilation error
acerone85 Oct 16, 2024
6cebf0e
Placate Clippy
acerone85 Oct 16, 2024
939939d
Version PostcardCodec
acerone85 Oct 16, 2024
e8c563c
Revert "Revert changes"
acerone85 Oct 17, 2024
f50a4d3
P2P Service: Add /fuel/req_res/0.0.2 that sends error codes in respon…
acerone85 Oct 17, 2024
77841f0
Prefer V2 over V1 when peers support both versions of the protocol
acerone85 Oct 21, 2024
7160034
Changelog
acerone85 Oct 21, 2024
b2b5b3c
PostcardProtocol default implementation is now derived
acerone85 Oct 21, 2024
93b4d06
Add tests to check serialization and deserialization of responses
acerone85 Oct 21, 2024
06e4177
Formatting
acerone85 Oct 21, 2024
24dd5db
Request/response: Return meaningful error codes wehen using version 0…
acerone85 Oct 21, 2024
1bc4253
Log response error codes as warnings
acerone85 Oct 21, 2024
a092a03
Merge branch 'master' into 1311-return-better-error-messages-from-p2p…
acerone85 Oct 22, 2024
c76608b
Fix inconsistencies from git merge master
acerone85 Oct 22, 2024
aa79b3c
add changelog entry
acerone85 Oct 22, 2024
ea4842c
Remove default implementation for PostcardProtocol
acerone85 Oct 23, 2024
d10dca8
Merge branch 'master' into 1311-return-better-error-messages-from-p2p…
acerone85 Oct 28, 2024
ea8666d
Merge branch 'master' into 1311-return-better-error-messages-from-p2p…
acerone85 Oct 31, 2024
216dfb9
Fix tests compilation
acerone85 Oct 31, 2024
fd2ba04
Remove stale todos
acerone85 Oct 31, 2024
5ea9b30
Add unknown error code
acerone85 Oct 31, 2024
8ca1dba
Merge branch 'master' into 1311-return-better-error-messages-from-p2p…
acerone85 Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Changed
- [2378](https://github.com/FuelLabs/fuel-core/pull/2378): Use cached hash of the topic instead of calculating it on each publishing gossip message.
- [2377](https://github.com/FuelLabs/fuel-core/pull/2377): Add more errors that can be returned as responses when using protocol `/fuel/req_res/0.0.2`. The errors supported are `ProtocolV1EmptyResponse` (status code `0`) for converting empty responses sent via protocol `/fuel/req_res/0.0.1`, `RequestedRangeTooLarge`(status code `1`) if the client requests a range of objects such as sealed block headers or transactions too large, `Timeout` (status code `2`) if the remote peer takes too long to fulfill a request, or `SyncProcessorOutOfCapacity` if the remote peer is fulfilling too many requests concurrently.

#### Breaking
- [2389](https://github.com/FuelLabs/fuel-core/pull/2258): Updated the `messageProof` GraphQL schema to return a non-nullable `MessageProof`.
Expand Down
14 changes: 6 additions & 8 deletions crates/services/p2p/src/codecs/postcard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,16 +180,17 @@ impl NetworkCodec for PostcardCodec {
fn get_req_res_protocols(
&self,
) -> impl Iterator<Item = <Self as request_response::Codec>::Protocol> {
// TODO: Iterating over versions in reverse order should prefer
// TODO: https://github.com/FuelLabs/fuel-core/issues/2374
// Iterating over versions in reverse order should prefer
// peers to use V2 over V1 for exchanging messages. However, this is
// not guaranteed by the specs for the `request_response` protocol.
// not guaranteed by the specs for the `request_response` protocol,
// and it should be tested.
PostcardProtocol::iter().rev()
}
}

#[derive(Debug, Default, Clone, EnumIter)]
#[derive(Debug, Clone, EnumIter)]
pub enum PostcardProtocol {
#[default]
V1,
V2,
}
Expand All @@ -206,7 +207,6 @@ impl AsRef<str> for PostcardProtocol {
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {

use fuel_core_types::blockchain::SealedBlockHeader;
use request_response::Codec as _;

Expand Down Expand Up @@ -310,10 +310,8 @@ mod tests {
async fn codec__serialzation_roundtrip_using_v1_on_error_response_returns_predefined_error_code(
) {
// Given
// TODO: https://github.com/FuelLabs/fuel-core/issues/1311
// Change this to a different ResponseMessageErrorCode once these have been implemented.
let response = V2ResponseMessage::SealedHeaders(Err(
ResponseMessageErrorCode::ProtocolV1EmptyResponse,
ResponseMessageErrorCode::RequestedRangeTooLarge,
));
let mut codec = PostcardCodec::new(1024);
let mut buf = Vec::with_capacity(1024);
Expand Down
28 changes: 13 additions & 15 deletions crates/services/p2p/src/p2p_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,9 +675,7 @@ impl FuelP2PService {
let send_ok = match channel {
ResponseSender::SealedHeaders(c) => match response {
V2ResponseMessage::SealedHeaders(v) => {
// TODO: https://github.com/FuelLabs/fuel-core/issues/1311
// Change type of ResponseSender and remove the .ok() here
c.send(Ok((peer, Ok(v.ok())))).is_ok()
c.send(Ok((peer, Ok(v)))).is_ok()
}
_ => {
warn!(
Expand All @@ -690,7 +688,7 @@ impl FuelP2PService {
},
ResponseSender::Transactions(c) => match response {
V2ResponseMessage::Transactions(v) => {
c.send(Ok((peer, Ok(v.ok())))).is_ok()
c.send(Ok((peer, Ok(v)))).is_ok()
}
_ => {
warn!(
Expand All @@ -703,7 +701,7 @@ impl FuelP2PService {
},
ResponseSender::TransactionsFromPeer(c) => match response {
V2ResponseMessage::Transactions(v) => {
c.send((peer, Ok(v.ok()))).is_ok()
c.send((peer, Ok(v))).is_ok()
}
_ => {
warn!(
Expand All @@ -715,7 +713,7 @@ impl FuelP2PService {
},
ResponseSender::TxPoolAllTransactionsIds(c) => match response {
V2ResponseMessage::TxPoolAllTransactionsIds(v) => {
c.send((peer, Ok(v.ok()))).is_ok()
c.send((peer, Ok(v))).is_ok()
}
_ => {
warn!(
Expand All @@ -727,7 +725,7 @@ impl FuelP2PService {
},
ResponseSender::TxPoolFullTransactions(c) => match response {
V2ResponseMessage::TxPoolFullTransactions(v) => {
c.send((peer, Ok(v.ok()))).is_ok()
c.send((peer, Ok(v))).is_ok()
}
_ => {
warn!(
Expand Down Expand Up @@ -1719,12 +1717,12 @@ mod tests {

if let Ok(response) = response_message {
match response {
Ok((_, Ok(Some(sealed_headers)))) => {
Ok((_, Ok(Ok(sealed_headers)))) => {
let check = expected.iter().zip(sealed_headers.iter()).all(|(a, b)| eq_except_metadata(a, b));
let _ = tx_test_end.send(check).await;
},
Ok((_, Ok(None))) => {
tracing::error!("Node A did not return any headers");
Ok((_, Ok(Err(e)))) => {
tracing::error!("Node A did not return any headers: {:?}", e);
let _ = tx_test_end.send(false).await;
},
Ok((_, Err(e))) => {
Expand Down Expand Up @@ -1752,12 +1750,12 @@ mod tests {

if let Ok(response) = response_message {
match response {
Ok((_, Ok(Some(transactions)))) => {
Ok((_, Ok(Ok(transactions)))) => {
let check = transactions.len() == 1 && transactions[0].0.len() == 5;
let _ = tx_test_end.send(check).await;
},
Ok((_, Ok(None))) => {
tracing::error!("Node A did not return any transactions");
Ok((_, Ok(Err(e)))) => {
tracing::error!("Node A did not return any transactions: {:?}", e);
let _ = tx_test_end.send(false).await;
},
Ok((_, Err(e))) => {
Expand All @@ -1782,7 +1780,7 @@ mod tests {
tokio::spawn(async move {
let response_message = rx_orchestrator.await;

if let Ok((_, Ok(Some(transaction_ids)))) = response_message {
if let Ok((_, Ok(Ok(transaction_ids)))) = response_message {
let tx_ids: Vec<TxId> = (0..5).map(|_| Transaction::default_test_tx().id(&ChainId::new(1))).collect();
let check = transaction_ids.len() == 5 && transaction_ids.iter().zip(tx_ids.iter()).all(|(a, b)| a == b);
let _ = tx_test_end.send(check).await;
Expand All @@ -1799,7 +1797,7 @@ mod tests {
tokio::spawn(async move {
let response_message = rx_orchestrator.await;

if let Ok((_, Ok(Some(transactions)))) = response_message {
if let Ok((_, Ok(Ok(transactions)))) = response_message {
let txs: Vec<Option<NetworkableTransactionPool>> = tx_ids.iter().enumerate().map(|(i, _)| {
if i == 0 {
None
Expand Down
69 changes: 64 additions & 5 deletions crates/services/p2p/src/request_response/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ pub enum ResponseMessageErrorCode {
/// The peer sent an empty response using protocol `/fuel/req_res/0.0.1`
#[error("Empty response sent by peer using legacy protocol /fuel/req_res/0.0.1")]
ProtocolV1EmptyResponse = 0,
#[error("The requested range is too large")]
RequestedRangeTooLarge = 1,
#[error("Timeout while processing request")]
Timeout = 2,
#[error("Sync processor is out of capacity")]
SyncProcessorOutOfCapacity = 3,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, maybe we need to add Unknown(u32) for backward compatibility in the case if we can't deserialize the error.

Copy link
Contributor Author

@acerone85 acerone85 Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried something in 5ea9b30. There is an unknown variant, but the error code is erased away. The reason for this is that deserializing unknown values in serde has been recently implemented via the attribute serde(untagged), but this is not supported by postcard (fails with error WontImplement). See serde-rs/serde#912 (comment) for the discussion on serde(untagged). There are other alternatives proposed, such as nested enums or custom deserialization functions for a variant, but they won't either work with postcard or are too complicated imho.

Adding an Unknown variant which abstracts away the error code does at least ensure backwards compatibility.

#[error("The peer sent an unknown error code")]
#[serde(skip_serializing, other)]
Unknown,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -113,11 +122,22 @@ pub type OnResponseWithPeerSelection<T> =

#[derive(Debug)]
pub enum ResponseSender {
SealedHeaders(OnResponseWithPeerSelection<Option<Vec<SealedBlockHeader>>>),
Transactions(OnResponseWithPeerSelection<Option<Vec<Transactions>>>),
TransactionsFromPeer(OnResponse<Option<Vec<Transactions>>>),
TxPoolAllTransactionsIds(OnResponse<Option<Vec<TxId>>>),
TxPoolFullTransactions(OnResponse<Option<Vec<Option<NetworkableTransactionPool>>>>),
SealedHeaders(
OnResponseWithPeerSelection<
Result<Vec<SealedBlockHeader>, ResponseMessageErrorCode>,
>,
),
Transactions(
OnResponseWithPeerSelection<Result<Vec<Transactions>, ResponseMessageErrorCode>>,
),
TransactionsFromPeer(OnResponse<Result<Vec<Transactions>, ResponseMessageErrorCode>>),

TxPoolAllTransactionsIds(OnResponse<Result<Vec<TxId>, ResponseMessageErrorCode>>),
TxPoolFullTransactions(
OnResponse<
Result<Vec<Option<NetworkableTransactionPool>>, ResponseMessageErrorCode>,
>,
),
}

#[derive(Debug, Error)]
Expand Down Expand Up @@ -146,3 +166,42 @@ pub enum ResponseSendError {
#[error("Failed to convert response to intermediate format")]
ConversionToIntermediateFailed,
}

#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use super::ResponseMessageErrorCode;

#[test]
fn response_message_error_code__unknown_error_cannot_be_serialized() {
let error = super::ResponseMessageErrorCode::Unknown;
let serialized = postcard::to_allocvec(&error);
assert!(serialized.is_err());
}

#[test]
fn response_message_error_code__known_error_code_is_deserialized_to_variant() {
let serialized_error_code =
postcard::to_stdvec(&ResponseMessageErrorCode::ProtocolV1EmptyResponse)
.unwrap();
println!("Error code: {:?}", serialized_error_code);
let response_message_error_code: ResponseMessageErrorCode =
postcard::from_bytes(&serialized_error_code).unwrap();
assert!(matches!(
response_message_error_code,
ResponseMessageErrorCode::ProtocolV1EmptyResponse
));
}

#[test]
fn response_message_error_code__unknown_error_code_is_deserialized_to_unknown_variant(
) {
let serialized_error_code = vec![42];
let response_message_error_code: ResponseMessageErrorCode =
postcard::from_bytes(&serialized_error_code).unwrap();
assert!(matches!(
response_message_error_code,
ResponseMessageErrorCode::Unknown
));
}
}
Loading
Loading