1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use http::uri::Uri;
use ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient;
use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, EthAccount, QueryAccountRequest};
use prost::Message;
use tracing::info;

use crate::chain::cosmos::types::account::Account;
use crate::config::default::max_grpc_decoding_size;
use crate::error::Error;

/// Get a `&mut Account` from an `&mut Option<Account>` if it is `Some(Account)`.
/// Otherwise query for the account information, update the `Option` to `Some`,
/// and return the underlying `&mut` reference.
pub async fn get_or_fetch_account<'a>(
    grpc_address: &'a Uri,
    account_address: &'a str,
    m_account: &'a mut Option<Account>,
) -> Result<&'a mut Account, Error> {
    match m_account {
        Some(account) => Ok(account),
        None => {
            let account = query_account(grpc_address, account_address).await?;
            *m_account = Some(account.into());

            Ok(m_account
                .as_mut()
                .expect("account was supposedly just cached"))
        }
    }
}

/// Refresh the account sequence behind the `&mut Account` by refetching the
/// account and updating the `&mut` reference.
pub async fn refresh_account<'a>(
    grpc_address: &Uri,
    account_address: &str,
    m_account: &'a mut Account,
) -> Result<(), Error> {
    let account = query_account(grpc_address, account_address).await?;

    info!(
        old = %m_account.sequence,
        new = %account.sequence,
        "refreshed account sequence number",
    );

    *m_account = account.into();

    Ok(())
}

/// Uses the GRPC client to retrieve the account sequence
pub async fn query_account(
    grpc_address: &Uri,
    account_address: &str,
) -> Result<BaseAccount, Error> {
    let mut client = QueryClient::connect(grpc_address.clone())
        .await
        .map_err(Error::grpc_transport)?;

    client = client.max_decoding_message_size(max_grpc_decoding_size().get_bytes() as usize);

    let request = tonic::Request::new(QueryAccountRequest {
        address: account_address.to_string(),
    });

    let response = client.account(request).await;

    // Querying for an account might fail, i.e. if the account doesn't actually exist
    let resp_account = match response
        .map_err(|e| Error::grpc_status(e, "query_account".to_owned()))?
        .into_inner()
        .account
    {
        Some(account) => account,
        None => return Err(Error::empty_query_account(account_address.to_string())),
    };

    if resp_account.type_url == "/cosmos.auth.v1beta1.BaseAccount" {
        Ok(BaseAccount::decode(resp_account.value.as_slice())
            .map_err(|e| Error::protobuf_decode("BaseAccount".to_string(), e))?)
    } else if resp_account.type_url.ends_with(".EthAccount") {
        Ok(EthAccount::decode(resp_account.value.as_slice())
            .map_err(|e| Error::protobuf_decode("EthAccount".to_string(), e))?
            .base_account
            .ok_or_else(Error::empty_base_account)?)
    } else {
        Err(Error::unknown_account_type(resp_account.type_url))
    }
}