-
Notifications
You must be signed in to change notification settings - Fork 96
/
Copy pathestimate.rs
155 lines (131 loc) · 4.55 KB
/
estimate.rs
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use ibc::core::ics24_host::identifier::ChainId;
use ibc_proto::cosmos::tx::v1beta1::{Fee, Tx};
use ibc_proto::google::protobuf::Any;
use tonic::codegen::http::Uri;
use tracing::{debug, error, span, warn, Level};
use crate::chain::cosmos::encode::sign_tx;
use crate::chain::cosmos::gas::{gas_amount_to_fees, PrettyFee};
use crate::chain::cosmos::simulate::send_tx_simulate;
use crate::chain::cosmos::types::account::Account;
use crate::chain::cosmos::types::gas::GasConfig;
use crate::config::types::Memo;
use crate::config::ChainConfig;
use crate::error::Error;
use crate::keyring::KeyEntry;
pub async fn estimate_tx_fees(
config: &ChainConfig,
grpc_address: &Uri,
key_entry: &KeyEntry,
account: &Account,
tx_memo: &Memo,
messages: Vec<Any>,
) -> Result<Fee, Error> {
let gas_config = GasConfig::from_chain_config(config);
debug!(
"max fee, for use in tx simulation: {}",
PrettyFee(&gas_config.max_fee)
);
let signed_tx = sign_tx(
config,
key_entry,
account,
tx_memo,
messages,
&gas_config.max_fee,
)?;
let tx = Tx {
body: Some(signed_tx.body),
auth_info: Some(signed_tx.auth_info),
signatures: signed_tx.signatures,
};
let estimated_fee = estimate_fee_with_tx(&gas_config, grpc_address, &config.id, tx).await?;
Ok(estimated_fee)
}
async fn estimate_fee_with_tx(
gas_config: &GasConfig,
grpc_address: &Uri,
chain_id: &ChainId,
tx: Tx,
) -> Result<Fee, Error> {
let estimated_gas = estimate_gas_with_tx(gas_config, grpc_address, tx).await?;
if estimated_gas > gas_config.max_gas {
debug!(
id = %chain_id, estimated = ?estimated_gas, max = ?gas_config.max_gas,
"send_tx: estimated gas is higher than max gas"
);
return Err(Error::tx_simulate_gas_estimate_exceeded(
chain_id.clone(),
estimated_gas,
gas_config.max_gas,
));
}
let adjusted_fee = gas_amount_to_fees(gas_config, estimated_gas);
debug!(
id = %chain_id,
"send_tx: using {} gas, fee {}",
estimated_gas,
PrettyFee(&adjusted_fee)
);
Ok(adjusted_fee)
}
/// Try to simulate the given tx in order to estimate how much gas will be needed to submit it.
///
/// It is possible that a batch of messages are fragmented by the caller (`send_msgs`) such that
/// they do not individually verify. For example for the following batch:
/// [`MsgUpdateClient`, `MsgRecvPacket`, ..., `MsgRecvPacket`]
///
/// If the batch is split in two TX-es, the second one will fail the simulation in `deliverTx` check.
/// In this case we use the `default_gas` param.
async fn estimate_gas_with_tx(
gas_config: &GasConfig,
grpc_address: &Uri,
tx: Tx,
) -> Result<u64, Error> {
let simulated_gas = send_tx_simulate(grpc_address, tx)
.await
.map(|sr| sr.gas_info);
let _span = span!(Level::ERROR, "estimate_gas").entered();
match simulated_gas {
Ok(Some(gas_info)) => {
debug!(
"tx simulation successful, gas amount used: {:?}",
gas_info.gas_used
);
Ok(gas_info.gas_used)
}
Ok(None) => {
warn!(
"tx simulation successful but no gas amount used was returned, falling back on default gas: {}",
gas_config.default_gas
);
Ok(gas_config.default_gas)
}
// If there is a chance that the tx will be accepted once actually submitted, we fall
// back on the max gas and will attempt to send it anyway.
// See `can_recover_from_simulation_failure` for more info.
Err(e) if can_recover_from_simulation_failure(&e) => {
warn!(
"failed to simulate tx, falling back on default gas because the error is potentially recoverable: {}",
e.detail()
);
Ok(gas_config.default_gas)
}
Err(e) => {
error!(
"failed to simulate tx. propagating error to caller: {}",
e.detail()
);
// Propagate the error, the retrying mechanism at caller may catch & retry.
Err(e)
}
}
}
/// Determine whether the given error yielded by `tx_simulate`
/// can be recovered from by submitting the tx anyway.
fn can_recover_from_simulation_failure(e: &Error) -> bool {
use crate::error::ErrorDetail::*;
match e.detail() {
GrpcStatus(detail) => detail.is_client_state_height_too_low(),
_ => false,
}
}