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
26pub fn to_retry_error<T, E: std::fmt::Debug + Send + Sync>(
27    err: SendRequestError<E>,
28    is_critical_t: impl Fn(&SendRequestError<E>) -> bool,
29) -> RetryResponse<T, SendRequestError<E>> {
30    if is_critical_t(&err) {
31        RetryResponse::Critical(err)
32    } else {
33        RetryResponse::Retry(err)
34    }
35}
36
37pub fn is_critical_blocks_error(err: &SendRequestError<RpcBlockError>) -> bool {
38    is_critical_json_rpc_error(err, |err| match err {
39        RpcBlockError::UnknownBlock { .. }
40        | RpcBlockError::NotSyncedYet
41        | RpcBlockError::InternalError { .. } => false,
42        _ => false,
43    })
44}
45
46pub fn is_critical_validator_error(err: &SendRequestError<RpcValidatorError>) -> bool {
47    is_critical_json_rpc_error(err, |err| match err {
48        RpcValidatorError::UnknownEpoch
49        | RpcValidatorError::ValidatorInfoUnavailable
50        | RpcValidatorError::InternalError { .. } => false,
51        _ => false,
52    })
53}
54pub fn is_critical_query_error(err: &SendRequestError<RpcQueryError>) -> bool {
55    is_critical_json_rpc_error(err, |err| match err {
56        RpcQueryError::NoSyncedBlocks
57        | RpcQueryError::UnavailableShard { .. }
58        | RpcQueryError::UnknownBlock { .. }
59        | RpcQueryError::InternalError { .. } => false,
60
61        RpcQueryError::GarbageCollectedBlock { .. }
62        | RpcQueryError::InvalidAccount { .. }
63        | RpcQueryError::UnknownAccount { .. }
64        | RpcQueryError::NoContractCode { .. }
65        | RpcQueryError::TooLargeContractState { .. }
66        | RpcQueryError::UnknownAccessKey { .. }
67        | RpcQueryError::ContractExecutionError { .. }
68        | RpcQueryError::UnknownGasKey { .. } => true,
69
70        // Might be critical, but also might not yet propagated across the network, so we will retry
71        RpcQueryError::NoGlobalContractCode { .. } => false,
72        _ => false,
73    })
74}
75
76pub fn is_critical_transaction_error(err: &SendRequestError<RpcTransactionError>) -> bool {
77    is_critical_json_rpc_error(err, |err| match err {
78        RpcTransactionError::TimeoutError | RpcTransactionError::RequestRouted { .. } => false,
79        RpcTransactionError::InvalidTransaction { .. }
80        | RpcTransactionError::DoesNotTrackShard
81        | RpcTransactionError::UnknownTransaction { .. }
82        | RpcTransactionError::InternalError { .. } => true,
83        _ => false,
84    })
85}
86
87fn is_critical_json_rpc_error<RpcError: std::fmt::Debug + Send + Sync>(
88    err: &SendRequestError<RpcError>,
89    is_critical_t: impl Fn(&RpcError) -> bool,
90) -> bool {
91    match err {
92        SendRequestError::ServerError(rpc_error) => is_critical_t(rpc_error),
93        SendRequestError::WasmExecutionError(_) => true,
94        SendRequestError::InternalError { .. } => false,
95        SendRequestError::RequestValidationError(_) => true,
96        SendRequestError::RequestCreationError(_) => true,
97        SendRequestError::TransportError(error) => match error {
98            near_openapi_client::Error::InvalidRequest(_)
99            | near_openapi_client::Error::CommunicationError(_)
100            | near_openapi_client::Error::InvalidUpgrade(_)
101            | near_openapi_client::Error::ResponseBodyError(_)
102            | near_openapi_client::Error::InvalidResponsePayload(_, _)
103            | near_openapi_client::Error::UnexpectedResponse(_)
104            | near_openapi_client::Error::Custom(_) => true,
105
106            near_openapi_client::Error::ErrorResponse(response_value) => {
107                // It's more readable to use a match statement than a macro
108                #[allow(clippy::match_like_matches_macro)]
109                match response_value.status() {
110                    StatusCode::REQUEST_TIMEOUT
111                    | StatusCode::TOO_MANY_REQUESTS
112                    | StatusCode::INTERNAL_SERVER_ERROR
113                    | StatusCode::BAD_GATEWAY
114                    | StatusCode::SERVICE_UNAVAILABLE
115                    | StatusCode::GATEWAY_TIMEOUT => false,
116                    _ => true,
117                }
118            }
119            _ => false,
120        },
121        _ => false,
122    }
123}