Skip to content

Commit

Permalink
Implement a custom retryable strategy for sourcify reqwest middleware (
Browse files Browse the repository at this point in the history
…#617)

* Implement a custom retryable strategy for sourcify reqwest middleware

* Turn on the verify_from_etherscan_contract_not_verified' test case. Return sourcify lib version to v1.0.0.

* Refactor test error messages
  • Loading branch information
rimrakhimov authored Sep 26, 2023
1 parent 3c0c214 commit 34827ae
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 29 deletions.
4 changes: 2 additions & 2 deletions libs/sourcify/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sourcify"
version = "0.1.1"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -11,7 +11,7 @@ bytes = "1.4.0"
blockscout-display-bytes = "1.0.0"
reqwest = { version = "0.11.18", features = ["json"] }
reqwest-middleware = "0.2.2"
reqwest-retry = "0.2.2"
reqwest-retry = "0.3.0"
serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.96"
thiserror = "1.0.40"
Expand Down
96 changes: 69 additions & 27 deletions libs/sourcify/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,40 @@ use serde::{Deserialize, Serialize};
use std::{str::FromStr, sync::Arc};
use url::Url;

mod retryable_strategy {
use reqwest::StatusCode;
use reqwest_middleware::Error;
use reqwest_retry::{Retryable, RetryableStrategy};

pub struct SourcifyRetryableStrategy;

impl RetryableStrategy for SourcifyRetryableStrategy {
fn handle(&self, res: &Result<reqwest::Response, Error>) -> Option<Retryable> {
match res {
Ok(success) => default_on_request_success(success),
Err(error) => reqwest_retry::default_on_request_failure(error),
}
}
}

// The strategy differs from `reqwest_retry::default_on_request_success`
// by considering 500 errors as Fatal instead of Transient.
// The reason is that Sourcify uses 500 code to propagate fatal internal errors,
// which will not be resolved on retry and which we would like to get early to process.
fn default_on_request_success(success: &reqwest::Response) -> Option<Retryable> {
let status = success.status();
if status.is_server_error() && status != StatusCode::INTERNAL_SERVER_ERROR {
Some(Retryable::Transient)
} else if status.is_success() {
None
} else if status == StatusCode::REQUEST_TIMEOUT || status == StatusCode::TOO_MANY_REQUESTS {
Some(Retryable::Transient)
} else {
Some(Retryable::Fatal)
}
}
}

#[derive(Clone)]
pub struct ClientBuilder {
base_url: Url,
Expand Down Expand Up @@ -56,7 +90,10 @@ impl ClientBuilder {
pub fn build(self) -> Client {
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(self.max_retries);
let mut client_builder = reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
.with(RetryTransientMiddleware::new_with_policy(retry_policy));
.with(RetryTransientMiddleware::new_with_policy_and_strategy(
retry_policy,
retryable_strategy::SourcifyRetryableStrategy,
));
for middleware in self.middleware_stack {
client_builder = client_builder.with_arc(middleware);
}
Expand Down Expand Up @@ -246,7 +283,7 @@ mod tests {
fn client() -> Client {
let rate_limiter = rate_limiter_middleware().clone();
ClientBuilder::default()
.max_retries(1)
.max_retries(3)
.with_arc_middleware(rate_limiter)
.build()
}
Expand Down Expand Up @@ -393,7 +430,7 @@ mod tests {
.expect_err("error expected");
assert!(
matches!(result, Error::Sourcify(SourcifyError::ChainNotSupported(_))),
"expected: 'SourcifyError::BadRequest', got: {result:?}"
"expected: 'SourcifyError::ChainNotSupported', got: {result:?}"
);
}

Expand All @@ -417,32 +454,37 @@ mod tests {
.verify_from_etherscan(chain_id, contract_address)
.await
.expect_err("error expected");
assert!(matches!(
result,
Error::Sourcify(SourcifyError::Custom(
VerifyFromEtherscanError::ChainNotSupported(_)
))
));
assert!(
matches!(
result,
Error::Sourcify(SourcifyError::Custom(
VerifyFromEtherscanError::ChainNotSupported(_)
))
),
"expected: 'SourcifyError::ChainNotSupported', got: {result:?}"
);
}

// TODO: returns TooManyRequests to the Etherscan API for some reason
// #[tokio::test]
// async fn verify_from_etherscan_contract_not_verified() {
// let chain_id = "5";
// let contract_address =
// parse_contract_address("0x847F2d0c193E90963aAD7B2791aAE8d7310dFF6A");
//
// let result = client()
// .verify_from_etherscan(chain_id, contract_address)
// .await
// .expect_err("error expected");
// assert!(matches!(
// result,
// Error::Sourcify(SourcifyError::Custom(
// VerifyFromEtherscanError::ContractNotVerified(_)
// ))
// ));
// }
#[tokio::test]
async fn verify_from_etherscan_contract_not_verified() {
let chain_id = "5";
let contract_address =
parse_contract_address("0x847F2d0c193E90963aAD7B2791aAE8d7310dFF6A");

let result = client()
.verify_from_etherscan(chain_id, contract_address)
.await
.expect_err("error expected");
assert!(
matches!(
result,
Error::Sourcify(SourcifyError::Custom(
VerifyFromEtherscanError::ContractNotVerified(_)
))
),
"expected: 'SourcifyError::ContractNotVerified', got: {result:?}"
);
}

/*
* Not implemented to avoid unnecessary burden on the Sourcify server.
Expand Down

0 comments on commit 34827ae

Please sign in to comment.