near_api/common/
utils.rs

1// New errors can be added to the codebase, so we want to handle them gracefully
2#![allow(unreachable_patterns)]
3
4use base64::{prelude::BASE64_STANDARD, Engine};
5use near_api_types::NearToken;
6use near_openapi_client::types::{
7    RpcBlockError, RpcQueryError, RpcTransactionError, RpcValidatorError,
8};
9use reqwest::StatusCode;
10
11use crate::{config::RetryResponse, errors::SendRequestError};
12
13pub fn to_base64(input: &[u8]) -> String {
14    BASE64_STANDARD.encode(input)
15}
16
17pub fn from_base64(encoded: &str) -> Result<Vec<u8>, base64::DecodeError> {
18    BASE64_STANDARD.decode(encoded)
19}
20
21/// Converts [crate::Data]<[u128]>] to [crate::NearToken].
22pub const fn near_data_to_near_token(data: near_api_types::Data<u128>) -> NearToken {
23    NearToken::from_yoctonear(data.data)
24}
25
26// TODO: this is a temporary solution to check if an error is critical
27// we had previously a full scale support for that
28// but auto generated code doesn't support errors yet, so we would need to leave it as is for now
29// We default to false as we can't know if an error is critical or not without the types
30// so to keep it safe it's better to retry
31
32pub fn to_retry_error<T, E: std::fmt::Debug + Send + Sync>(
33    err: SendRequestError<E>,
34    is_critical_t: impl Fn(&SendRequestError<E>) -> bool,
35) -> RetryResponse<T, SendRequestError<E>> {
36    if is_critical_t(&err) {
37        RetryResponse::Critical(err)
38    } else {
39        RetryResponse::Retry(err)
40    }
41}
42
43pub fn is_critical_blocks_error(err: &SendRequestError<RpcBlockError>) -> bool {
44    is_critical_json_rpc_error(err, |err| match err {
45        RpcBlockError::UnknownBlock { .. }
46        | RpcBlockError::NotSyncedYet
47        | RpcBlockError::InternalError { .. } => false,
48        _ => false,
49    })
50}
51
52pub fn is_critical_validator_error(err: &SendRequestError<RpcValidatorError>) -> bool {
53    is_critical_json_rpc_error(err, |err| match err {
54        RpcValidatorError::UnknownEpoch
55        | RpcValidatorError::ValidatorInfoUnavailable
56        | RpcValidatorError::InternalError { .. } => false,
57        _ => false,
58    })
59}
60pub fn is_critical_query_error(err: &SendRequestError<RpcQueryError>) -> bool {
61    is_critical_json_rpc_error(err, |err| match err {
62        RpcQueryError::NoSyncedBlocks
63        | RpcQueryError::UnavailableShard { .. }
64        | RpcQueryError::UnknownBlock { .. }
65        | RpcQueryError::InternalError { .. } => false,
66
67        RpcQueryError::GarbageCollectedBlock { .. }
68        | RpcQueryError::InvalidAccount { .. }
69        | RpcQueryError::UnknownAccount { .. }
70        | RpcQueryError::NoContractCode { .. }
71        | RpcQueryError::TooLargeContractState { .. }
72        | RpcQueryError::UnknownAccessKey { .. }
73        | RpcQueryError::ContractExecutionError { .. }
74        | RpcQueryError::UnknownGasKey { .. } => true,
75
76        // Might be critical, but also might not yet propagated across the network, so we will retry
77        RpcQueryError::NoGlobalContractCode { .. } => false,
78        _ => false,
79    })
80}
81
82pub fn is_critical_transaction_error(err: &SendRequestError<RpcTransactionError>) -> bool {
83    is_critical_json_rpc_error(err, |err| match err {
84        RpcTransactionError::TimeoutError | RpcTransactionError::RequestRouted { .. } => false,
85        RpcTransactionError::InvalidTransaction { .. }
86        | RpcTransactionError::DoesNotTrackShard
87        | RpcTransactionError::UnknownTransaction { .. }
88        | RpcTransactionError::InternalError { .. } => true,
89        _ => false,
90    })
91}
92
93fn is_critical_json_rpc_error<RpcError: std::fmt::Debug + Send + Sync>(
94    err: &SendRequestError<RpcError>,
95    is_critical_t: impl Fn(&RpcError) -> bool,
96) -> bool {
97    match err {
98        SendRequestError::ServerError(rpc_error) => is_critical_t(rpc_error),
99        SendRequestError::WasmExecutionError(_) => true,
100        SendRequestError::InternalError { .. } => false,
101        SendRequestError::RequestValidationError(_) => true,
102        SendRequestError::RequestCreationError(_) => true,
103        SendRequestError::TransportError(error) => match error {
104            near_openapi_client::Error::InvalidRequest(_)
105            | near_openapi_client::Error::CommunicationError(_)
106            | near_openapi_client::Error::InvalidUpgrade(_)
107            | near_openapi_client::Error::ResponseBodyError(_)
108            | near_openapi_client::Error::InvalidResponsePayload(_, _)
109            | near_openapi_client::Error::UnexpectedResponse(_)
110            | near_openapi_client::Error::Custom(_) => true,
111
112            near_openapi_client::Error::ErrorResponse(response_value) => {
113                // It's more readable to use a match statement than a macro
114                #[allow(clippy::match_like_matches_macro)]
115                match response_value.status() {
116                    StatusCode::REQUEST_TIMEOUT
117                    | StatusCode::TOO_MANY_REQUESTS
118                    | StatusCode::INTERNAL_SERVER_ERROR
119                    | StatusCode::BAD_GATEWAY
120                    | StatusCode::SERVICE_UNAVAILABLE
121                    | StatusCode::GATEWAY_TIMEOUT => false,
122                    _ => true,
123                }
124            }
125            _ => false,
126        },
127        _ => false,
128    }
129}