|
1 | 1 | use crate::error::{Error, Result}; |
2 | 2 | use alloy::{ |
| 3 | + network::Ethereum, |
3 | 4 | primitives::{Address, Bytes, U256}, |
4 | | - providers::Provider, |
| 5 | + providers::{PendingTransactionBuilder, Provider}, |
| 6 | + rpc::types::TransactionRequest, |
5 | 7 | }; |
6 | 8 | use fhevm_gateway_rust_bindings::decryption::Decryption; |
7 | | -use std::sync::Arc; |
8 | | -use tracing::{debug, info}; |
| 9 | +use std::{sync::Arc, time::Duration}; |
| 10 | +use tracing::{debug, info, warn}; |
| 11 | + |
| 12 | +/// The max value for the `gas_limit` of a transaction. |
| 13 | +const INFINITE_GAS_LIMIT: u64 = u64::MAX; |
| 14 | + |
| 15 | +/// The time to wait between two transactions attempt. |
| 16 | +const TX_INTERVAL: Duration = Duration::from_secs(3); |
9 | 17 |
|
10 | 18 | /// Adapter for decryption operations |
11 | 19 | #[derive(Clone)] |
@@ -57,18 +65,20 @@ impl<P: Provider + Clone> DecryptionAdapter<P> { |
57 | 65 |
|
58 | 66 | let contract = Decryption::new(self.decryption_address, self.provider.clone()); |
59 | 67 |
|
60 | | - // Create and send transaction |
61 | | - let call = contract.publicDecryptionResponse(id, result, signature.into()); |
62 | | - let tx = call |
63 | | - .send() |
64 | | - .await |
65 | | - .map_err(|e| Error::Contract(e.to_string()))?; |
| 68 | + let call_builder = contract.publicDecryptionResponse(id, result, signature.into()); |
| 69 | + info!(decryption_id = ?id, "public decryption calldata length {}", call_builder.calldata().len()); |
| 70 | + |
| 71 | + let mut call = call_builder.into_transaction_request(); |
| 72 | + self.estimate_gas(id, &mut call).await; |
| 73 | + let tx = self.send_tx_with_retry(call).await?; |
| 74 | + |
66 | 75 | // TODO: optimize for low latency |
67 | 76 | let receipt = tx |
68 | 77 | .get_receipt() |
69 | 78 | .await |
70 | 79 | .map_err(|e| Error::Contract(e.to_string()))?; |
71 | 80 | info!(decryption_id = ?id, "🎯 Public Decryption response sent with tx receipt: {:?}", receipt); |
| 81 | + info!(decryption_id = ?id, "⛽ Gas consumed for Public Decryption: {}", receipt.gas_used); |
72 | 82 | Ok(()) |
73 | 83 | } |
74 | 84 |
|
@@ -102,17 +112,58 @@ impl<P: Provider + Clone> DecryptionAdapter<P> { |
102 | 112 | let contract = Decryption::new(self.decryption_address, self.provider.clone()); |
103 | 113 |
|
104 | 114 | // Create and send transaction |
105 | | - let call = contract.userDecryptionResponse(id, result, signature.into()); |
106 | | - let tx = call |
107 | | - .send() |
108 | | - .await |
109 | | - .map_err(|e| Error::Contract(e.to_string()))?; |
| 115 | + let call_builder = contract.userDecryptionResponse(id, result, signature.into()); |
| 116 | + info!(decryption_id = ?id, "user decryption calldata length {}", call_builder.calldata().len()); |
| 117 | + |
| 118 | + let mut call = call_builder.into_transaction_request(); |
| 119 | + self.estimate_gas(id, &mut call).await; |
| 120 | + let tx = self.send_tx_with_retry(call).await?; |
| 121 | + |
110 | 122 | // TODO: optimize for low latency |
111 | 123 | let receipt = tx |
112 | 124 | .get_receipt() |
113 | 125 | .await |
114 | 126 | .map_err(|e| Error::Contract(e.to_string()))?; |
115 | 127 | info!(decryption_id = ?id, "🎯 User Decryption response sent with tx receipt: {:?}", receipt); |
| 128 | + info!(decryption_id = ?id, "⛽ Gas consumed for User Decryption: {}", receipt.gas_used); |
116 | 129 | Ok(()) |
117 | 130 | } |
| 131 | + |
| 132 | + /// Estimates the `gas_limit` for the upcoming transaction. |
| 133 | + async fn estimate_gas(&self, id: U256, call: &mut TransactionRequest) { |
| 134 | + match self.provider.estimate_gas(call.clone()).await { |
| 135 | + Ok(gas) => info!(decryption_id = ?id, "Initial gas estimation for the tx: {gas}"), |
| 136 | + Err(e) => warn!(decryption_id = ?id, "Failed to estimate gas for the tx: {e}"), |
| 137 | + } |
| 138 | + |
| 139 | + // TODO: temporary workaround for out-of-gas errors |
| 140 | + // Our automatic estimation fails during gas pikes. |
| 141 | + // (see https://zama-ai.slack.com/archives/C0915Q59CKG/p1749843623276629?thread_ts=1749828466.079719&cid=C0915Q59CKG) |
| 142 | + info!(decryption_id = ?id, "Updating `gas_limit` to max value"); |
| 143 | + call.gas = Some(INFINITE_GAS_LIMIT); |
| 144 | + } |
| 145 | + |
| 146 | + /// Sends the requested transactions with one retry. |
| 147 | + async fn send_tx_with_retry( |
| 148 | + &self, |
| 149 | + call: TransactionRequest, |
| 150 | + ) -> Result<PendingTransactionBuilder<Ethereum>> { |
| 151 | + match self.provider.send_transaction(call.clone()).await { |
| 152 | + Ok(tx) => Ok(tx), |
| 153 | + Err(e) => { |
| 154 | + warn!( |
| 155 | + "Retrying to send transaction in {}s after failure: {}", |
| 156 | + TX_INTERVAL.as_secs(), |
| 157 | + e |
| 158 | + ); |
| 159 | + |
| 160 | + tokio::time::sleep(TX_INTERVAL).await; |
| 161 | + |
| 162 | + self.provider |
| 163 | + .send_transaction(call) |
| 164 | + .await |
| 165 | + .map_err(|e| Error::Contract(e.to_string())) |
| 166 | + } |
| 167 | + } |
| 168 | + } |
118 | 169 | } |
0 commit comments