concordium_rust_sdk/v2/
dry_run.rs

1use super::{
2    generated::{
3        self, account_transaction_payload, dry_run_error_response, dry_run_request,
4        DryRunInvokeInstance, DryRunMintToAccount, DryRunSignature, DryRunStateOperation,
5    },
6    AccountIdentifier, IntoBlockIdentifier, Upward,
7};
8use crate::{
9    types::{
10        smart_contracts::{ContractContext, InstanceInfo, ReturnValue},
11        AccountInfo, AccountTransactionDetails, RejectReason,
12    },
13    v2::{generated::DryRunStateQuery, Require},
14};
15use concordium_base::{
16    base::{Energy, ProtocolVersion},
17    common::types::{CredentialIndex, KeyIndex, Timestamp},
18    contracts_common::{AccountAddress, Amount, ContractAddress},
19    hashes::BlockHash,
20    smart_contracts::ContractTraceElement,
21    transactions::{EncodedPayload, PayloadLike},
22};
23use futures::*;
24
25mod shared_receiver {
26    use futures::{stream::Stream, StreamExt};
27    use tokio::{
28        sync::{mpsc, oneshot},
29        task::JoinHandle,
30    };
31
32    /// A `SharedReceiver` wraps an underlying stream so that multiple clients
33    /// can queue to receive items from the stream.
34    pub struct SharedReceiver<I> {
35        senders: mpsc::UnboundedSender<oneshot::Sender<I>>,
36        task: JoinHandle<()>,
37    }
38
39    impl<I> Drop for SharedReceiver<I> {
40        fn drop(&mut self) {
41            self.task.abort();
42        }
43    }
44
45    impl<I> SharedReceiver<I>
46    where
47        I: Send + 'static,
48    {
49        /// Construct a new shared receiver. This spawns a background task that
50        /// pairs waiters with incoming stream items.
51        pub fn new(stream: impl Stream<Item = I> + Unpin + Send + 'static) -> Self {
52            let (senders, rec_senders) = mpsc::unbounded_channel::<oneshot::Sender<I>>();
53            let task = tokio::task::spawn(async {
54                let mut zipped = stream.zip(tokio_stream::wrappers::UnboundedReceiverStream::from(
55                    rec_senders,
56                ));
57                while let Some((item, sender)) = zipped.next().await {
58                    let _ = sender.send(item);
59                }
60            });
61            SharedReceiver { senders, task }
62        }
63
64        /// Claim the next item from the stream when it becomes available.
65        /// Returns `None` if the stream is already closed. Otherwise returns a
66        /// [`oneshot::Receiver`] that can be `await`ed to retrieve the item.
67        pub fn next(&self) -> Option<oneshot::Receiver<I>> {
68            let (send, recv) = oneshot::channel();
69            self.senders.send(send).ok()?;
70            Some(recv)
71        }
72    }
73}
74
75/// An error response to a dry-run request.
76#[derive(thiserror::Error, Debug)]
77pub enum ErrorResult {
78    /// The current block state is undefined. It should be initialized with a
79    /// `load_block_state` request before any other operations.
80    #[error("block state not loaded")]
81    NoState,
82    /// The requested block was not found, so its state could not be loaded.
83    /// Response to `load_block_state`.
84    #[error("block not found")]
85    BlockNotFound,
86    /// The specified account was not found.
87    /// Response to `get_account_info`, `mint_to_account` and `run_transaction`.
88    #[error("account not found")]
89    AccountNotFound,
90    /// The specified instance was not found.
91    /// Response to `get_instance_info`.
92    #[error("contract instance not found")]
93    InstanceNotFound,
94    /// The amount to mint would overflow the total CCD supply.
95    /// Response to `mint_to_account`.
96    #[error("mint amount exceeds limit")]
97    AmountOverLimit {
98        /// The maximum amount that can be minted.
99        amount_limit: Amount,
100    },
101    /// The balance of the sender account is not sufficient to pay for the
102    /// operation. Response to `run_transaction`.
103    #[error("account balance insufficient")]
104    BalanceInsufficient {
105        /// The balance required to pay for the operation.
106        required_amount: Amount,
107        /// The actual amount available on the account to pay for the operation.
108        available_amount: Amount,
109    },
110    /// The energy supplied for the transaction was not sufficient to perform
111    /// the basic checks. Response to `run_transaction`.
112    #[error("energy insufficient")]
113    EnergyInsufficient {
114        /// The energy required to perform the basic checks on the transaction.
115        /// Note that this may not be sufficient to also execute the
116        /// transaction.
117        energy_required: Energy,
118    },
119    /// The contract invocation failed.
120    /// Response to `invoke_instance`.
121    #[error("invoke instance failed")]
122    InvokeFailure {
123        /// If invoking a V0 contract this is not provided, otherwise it is
124        /// the return value produced by the call unless the call failed
125        /// with out of energy or runtime error. If the V1 contract
126        /// terminated with a logic error then the return value is
127        /// present.
128        return_value: Option<ReturnValue>,
129        /// Energy used by the execution.
130        used_energy: Energy,
131        /// Contract execution failed for the given reason.
132        reason: RejectReason,
133    },
134}
135
136impl TryFrom<dry_run_error_response::Error> for ErrorResult {
137    type Error = tonic::Status;
138
139    fn try_from(value: dry_run_error_response::Error) -> Result<Self, Self::Error> {
140        use dry_run_error_response::Error;
141        let res = match value {
142            Error::NoState(_) => Self::NoState,
143            Error::BlockNotFound(_) => Self::BlockNotFound,
144            Error::AccountNotFound(_) => Self::AccountNotFound,
145            Error::InstanceNotFound(_) => Self::InstanceNotFound,
146            Error::AmountOverLimit(e) => Self::AmountOverLimit {
147                amount_limit: e.amount_limit.require()?.into(),
148            },
149            Error::BalanceInsufficient(e) => Self::BalanceInsufficient {
150                required_amount: e.required_amount.require()?.into(),
151                available_amount: e.available_amount.require()?.into(),
152            },
153            Error::EnergyInsufficient(e) => Self::EnergyInsufficient {
154                energy_required: e.energy_required.require()?.into(),
155            },
156            Error::InvokeFailed(e) => Self::InvokeFailure {
157                return_value: e.return_value.map(ReturnValue::from),
158                used_energy: e.used_energy.require()?.into(),
159                reason: e.reason.require()?.try_into()?,
160            },
161        };
162        Ok(res)
163    }
164}
165
166/// An error resulting from a dry-run operation.
167#[derive(thiserror::Error, Debug)]
168pub enum DryRunError {
169    /// The server responded with an error code.
170    /// In this case, no futher requests will be accepted in the dry-run
171    /// session.
172    #[error("gRPC error: {0}")]
173    CallError(#[from] tonic::Status),
174    /// The dry-run operation failed.
175    /// In this case, further dry-run requests are possible in the same session.
176    #[error("dry-run operation failed: {result}")]
177    OperationFailed {
178        /// The error result.
179        #[source]
180        result: ErrorResult,
181        /// The energy quota remaining for subsequent dry-run requests in the
182        /// session.
183        quota_remaining: Energy,
184    },
185}
186
187/// A result value together with the remaining energy quota at the completion of
188/// the operation.
189#[derive(Debug, Clone)]
190pub struct WithRemainingQuota<T> {
191    /// The result value.
192    pub inner: T,
193    /// The remaining energy quota.
194    pub quota_remaining: Energy,
195}
196
197/// The successful result of [`DryRun::load_block_state`].
198#[derive(Debug, Clone)]
199pub struct BlockStateLoaded {
200    /// The timestamp of the block, taken to be the current timestamp when
201    /// executing transactions.
202    pub current_timestamp: Timestamp,
203    /// The hash of the block that was loaded.
204    pub block_hash: BlockHash,
205    /// The protocol version at the specified block. The behavior of operations
206    /// can vary across protocol version.
207    pub protocol_version: ProtocolVersion,
208}
209
210impl TryFrom<Option<Result<generated::DryRunResponse, tonic::Status>>>
211    for WithRemainingQuota<BlockStateLoaded>
212{
213    type Error = DryRunError;
214
215    fn try_from(
216        value: Option<Result<generated::DryRunResponse, tonic::Status>>,
217    ) -> Result<Self, Self::Error> {
218        let response =
219            value.ok_or_else(|| tonic::Status::cancelled("server closed dry run stream"))??;
220        let quota_remaining = response.quota_remaining.require()?.into();
221        use generated::dry_run_response::*;
222        match response.response.require()? {
223            Response::Error(e) => {
224                let result = e.error.require()?.try_into()?;
225                if !matches!(result, ErrorResult::BlockNotFound) {
226                    Err(tonic::Status::unknown("unexpected error response type"))?
227                }
228                Err(DryRunError::OperationFailed {
229                    result,
230                    quota_remaining,
231                })
232            }
233            Response::Success(s) => {
234                let response = s.response.require()?;
235                match response {
236                    generated::dry_run_success_response::Response::BlockStateLoaded(loaded) => {
237                        let protocol_version =
238                            generated::ProtocolVersion::try_from(loaded.protocol_version)
239                                .map_err(|_| tonic::Status::unknown("Unknown protocol version"))?
240                                .into();
241                        let loaded = BlockStateLoaded {
242                            current_timestamp: loaded.current_timestamp.require()?.into(),
243                            block_hash: loaded.block_hash.require()?.try_into()?,
244                            protocol_version,
245                        };
246                        Ok(WithRemainingQuota {
247                            inner: loaded,
248                            quota_remaining,
249                        })
250                    }
251                    _ => Err(tonic::Status::unknown("unexpected success response type"))?,
252                }
253            }
254        }
255    }
256}
257
258impl TryFrom<Option<Result<generated::DryRunResponse, tonic::Status>>>
259    for WithRemainingQuota<AccountInfo>
260{
261    type Error = DryRunError;
262
263    fn try_from(
264        value: Option<Result<generated::DryRunResponse, tonic::Status>>,
265    ) -> Result<Self, Self::Error> {
266        let response =
267            value.ok_or_else(|| tonic::Status::cancelled("server closed dry run stream"))??;
268        let quota_remaining = response.quota_remaining.require()?.into();
269        use generated::dry_run_response::*;
270        match response.response.require()? {
271            Response::Error(e) => {
272                let result = e.error.require()?.try_into()?;
273                if !matches!(result, ErrorResult::NoState | ErrorResult::AccountNotFound) {
274                    Err(tonic::Status::unknown("unexpected error response type"))?
275                }
276                Err(DryRunError::OperationFailed {
277                    result,
278                    quota_remaining,
279                })
280            }
281            Response::Success(s) => {
282                let response = s.response.require()?;
283                use generated::dry_run_success_response::*;
284                match response {
285                    Response::AccountInfo(info) => Ok(WithRemainingQuota {
286                        inner: info.try_into()?,
287                        quota_remaining,
288                    }),
289                    _ => Err(tonic::Status::unknown("unexpected success response type"))?,
290                }
291            }
292        }
293    }
294}
295
296impl TryFrom<Option<Result<generated::DryRunResponse, tonic::Status>>>
297    for WithRemainingQuota<InstanceInfo>
298{
299    type Error = DryRunError;
300
301    fn try_from(
302        value: Option<Result<generated::DryRunResponse, tonic::Status>>,
303    ) -> Result<Self, Self::Error> {
304        let response =
305            value.ok_or_else(|| tonic::Status::cancelled("server closed dry run stream"))??;
306        let quota_remaining = response.quota_remaining.require()?.into();
307        use generated::dry_run_response::*;
308        match response.response.require()? {
309            Response::Error(e) => {
310                let result = e.error.require()?.try_into()?;
311                if !matches!(result, ErrorResult::NoState | ErrorResult::InstanceNotFound) {
312                    Err(tonic::Status::unknown("unexpected error response type"))?
313                }
314                Err(DryRunError::OperationFailed {
315                    result,
316                    quota_remaining,
317                })
318            }
319            Response::Success(s) => {
320                let response = s.response.require()?;
321                use generated::dry_run_success_response::*;
322                match response {
323                    Response::InstanceInfo(info) => Ok(WithRemainingQuota {
324                        inner: info.try_into()?,
325                        quota_remaining,
326                    }),
327                    _ => Err(tonic::Status::unknown("unexpected success response type"))?,
328                }
329            }
330        }
331    }
332}
333
334impl From<&ContractContext> for DryRunInvokeInstance {
335    fn from(context: &ContractContext) -> Self {
336        DryRunInvokeInstance {
337            invoker: context.invoker.as_ref().map(|a| a.into()),
338            instance: Some((&context.contract).into()),
339            amount: Some(context.amount.into()),
340            entrypoint: Some(context.method.as_receive_name().into()),
341            parameter: Some(context.parameter.as_ref().into()),
342            energy: context.energy.map(From::from),
343        }
344    }
345}
346
347/// The successful result of [`DryRun::invoke_instance`].
348#[derive(Debug, Clone)]
349pub struct InvokeInstanceSuccess {
350    /// The return value for a V1 contract call. Absent for a V0 contract call.
351    pub return_value: Option<ReturnValue>,
352    /// The effects produced by contract execution.
353    pub events: Vec<Upward<ContractTraceElement>>,
354    /// The energy used by the execution.
355    pub used_energy: Energy,
356}
357
358impl TryFrom<Option<Result<generated::DryRunResponse, tonic::Status>>>
359    for WithRemainingQuota<InvokeInstanceSuccess>
360{
361    type Error = DryRunError;
362
363    fn try_from(
364        value: Option<Result<generated::DryRunResponse, tonic::Status>>,
365    ) -> Result<Self, Self::Error> {
366        let response =
367            value.ok_or_else(|| tonic::Status::cancelled("server closed dry run stream"))??;
368        let quota_remaining = response.quota_remaining.require()?.into();
369        use generated::dry_run_response::*;
370        match response.response.require()? {
371            Response::Error(e) => {
372                let result = e.error.require()?.try_into()?;
373                if !matches!(
374                    result,
375                    ErrorResult::NoState
376                        | ErrorResult::InvokeFailure {
377                            return_value: _,
378                            used_energy: _,
379                            reason: _,
380                        }
381                ) {
382                    Err(tonic::Status::unknown("unexpected error response type"))?
383                }
384                Err(DryRunError::OperationFailed {
385                    result,
386                    quota_remaining,
387                })
388            }
389            Response::Success(s) => {
390                let response = s.response.require()?;
391                use generated::dry_run_success_response::*;
392                match response {
393                    Response::InvokeSucceeded(result) => {
394                        let inner = InvokeInstanceSuccess {
395                            return_value: result.return_value.map(|a| ReturnValue { value: a }),
396                            events: result
397                                .effects
398                                .into_iter()
399                                .map(|trace| {
400                                    Ok(Upward::from(
401                                        trace
402                                            .element
403                                            .map(ContractTraceElement::try_from)
404                                            .transpose()?,
405                                    ))
406                                })
407                                .collect::<Result<_, tonic::Status>>()?,
408                            used_energy: result.used_energy.require()?.into(),
409                        };
410                        Ok(WithRemainingQuota {
411                            inner,
412                            quota_remaining,
413                        })
414                    }
415                    _ => Err(tonic::Status::unknown("unexpected success response type"))?,
416                }
417            }
418        }
419    }
420}
421
422/// The successful result of [`DryRun::set_timestamp`].
423#[derive(Clone, Debug, Copy)]
424pub struct TimestampSet;
425
426impl TryFrom<Option<Result<generated::DryRunResponse, tonic::Status>>>
427    for WithRemainingQuota<TimestampSet>
428{
429    type Error = DryRunError;
430
431    fn try_from(
432        value: Option<Result<generated::DryRunResponse, tonic::Status>>,
433    ) -> Result<Self, Self::Error> {
434        let response =
435            value.ok_or_else(|| tonic::Status::cancelled("server closed dry run stream"))??;
436        let quota_remaining = response.quota_remaining.require()?.into();
437        use generated::dry_run_response::*;
438        match response.response.require()? {
439            Response::Error(e) => {
440                let result = e.error.require()?.try_into()?;
441                if !matches!(result, ErrorResult::NoState) {
442                    Err(tonic::Status::unknown("unexpected error response type"))?
443                }
444                Err(DryRunError::OperationFailed {
445                    result,
446                    quota_remaining,
447                })
448            }
449            Response::Success(s) => {
450                let response = s.response.require()?;
451                match response {
452                    generated::dry_run_success_response::Response::TimestampSet(_) => {
453                        let inner = TimestampSet {};
454                        Ok(WithRemainingQuota {
455                            inner,
456                            quota_remaining,
457                        })
458                    }
459                    _ => Err(tonic::Status::unknown("unexpected success response type"))?,
460                }
461            }
462        }
463    }
464}
465
466/// The successful result of [`DryRun::mint_to_account`].
467#[derive(Clone, Debug, Copy)]
468pub struct MintedToAccount;
469
470impl TryFrom<Option<Result<generated::DryRunResponse, tonic::Status>>>
471    for WithRemainingQuota<MintedToAccount>
472{
473    type Error = DryRunError;
474
475    fn try_from(
476        value: Option<Result<generated::DryRunResponse, tonic::Status>>,
477    ) -> Result<Self, Self::Error> {
478        let response =
479            value.ok_or_else(|| tonic::Status::cancelled("server closed dry run stream"))??;
480        let quota_remaining = response.quota_remaining.require()?.into();
481        use generated::dry_run_response::*;
482        match response.response.require()? {
483            Response::Error(e) => {
484                let result = e.error.require()?.try_into()?;
485                if !matches!(
486                    result,
487                    ErrorResult::NoState | ErrorResult::AmountOverLimit { amount_limit: _ }
488                ) {
489                    Err(tonic::Status::unknown("unexpected error response type"))?
490                }
491                Err(DryRunError::OperationFailed {
492                    result,
493                    quota_remaining,
494                })
495            }
496            Response::Success(s) => {
497                let response = s.response.require()?;
498                match response {
499                    generated::dry_run_success_response::Response::MintedToAccount(_) => {
500                        let inner = MintedToAccount {};
501                        Ok(WithRemainingQuota {
502                            inner,
503                            quota_remaining,
504                        })
505                    }
506                    _ => Err(tonic::Status::unknown("unexpected success response type"))?,
507                }
508            }
509        }
510    }
511}
512
513/// Representation of a transaction for the purposes of dry-running it.
514/// Compared to a genuine transaction, this does not include an expiry time or
515/// signatures. It is possible to specify which credentials and keys are assumed
516/// to sign the transaction. This is only useful for transactions from
517/// multi-signature accounts. In particular, it can ensure that the calculated
518/// cost is correct when multiple signatures are used. For transactions that
519/// update the keys on a multi-credential account, the transaction must be
520/// signed by the credential whose keys are updated, so specifying which keys
521/// sign is required here.
522#[derive(Clone, Debug)]
523pub struct DryRunTransaction {
524    /// The account originating the transaction.
525    pub sender: AccountAddress,
526    /// The limit on the energy that may be used by the transaction.
527    pub energy_amount: Energy,
528    /// The transaction payload to execute.
529    pub payload: EncodedPayload,
530    /// The credential-keys that are treated as signing the transaction.
531    /// If this is the empty vector, it is treated as the single key (0,0)
532    /// signing.
533    pub signatures: Vec<(CredentialIndex, KeyIndex)>,
534}
535
536impl DryRunTransaction {
537    /// Create a [`DryRunTransaction`] given the sender address, energy limit
538    /// and payload. The empty list is used for the signatures, meaning that
539    /// it will be treated as though key 0 of credential 0 is the sole
540    /// signature on the transaction. For most purposes, this is sufficient.
541    pub fn new(sender: AccountAddress, energy_amount: Energy, payload: &impl PayloadLike) -> Self {
542        DryRunTransaction {
543            sender,
544            energy_amount,
545            payload: payload.encode(),
546            signatures: vec![],
547        }
548    }
549}
550
551impl<P: PayloadLike> From<concordium_base::transactions::AccountTransaction<P>>
552    for DryRunTransaction
553{
554    fn from(value: concordium_base::transactions::AccountTransaction<P>) -> Self {
555        DryRunTransaction {
556            sender: value.header.sender,
557            energy_amount: value.header.energy_amount,
558            payload: value.payload.encode(),
559            signatures: value
560                .signature
561                .signatures
562                .into_iter()
563                .flat_map(|(c, v)| std::iter::repeat(c).zip(v.into_keys()))
564                .collect(),
565        }
566    }
567}
568
569impl From<DryRunTransaction> for generated::DryRunTransaction {
570    fn from(transaction: DryRunTransaction) -> Self {
571        let payload = account_transaction_payload::Payload::RawPayload(transaction.payload.into());
572        generated::DryRunTransaction {
573            sender: Some(transaction.sender.into()),
574            energy_amount: Some(transaction.energy_amount.into()),
575            payload: Some(generated::AccountTransactionPayload {
576                payload: Some(payload),
577            }),
578            signatures: transaction
579                .signatures
580                .into_iter()
581                .map(|(cred, key)| DryRunSignature {
582                    credential: cred.index.into(),
583                    key: key.0.into(),
584                })
585                .collect(),
586        }
587    }
588}
589
590/// The successful result of [`DryRun::run_transaction`].
591/// Note that a transaction can still be rejected (i.e. produce no effect beyond
592/// charging the sender) even if it is executed.
593#[derive(Clone, Debug)]
594pub struct TransactionExecuted {
595    /// The actual energy cost of executing the transaction.
596    pub energy_cost: Energy,
597    /// Detailed result of the transaction execution.
598    pub details: AccountTransactionDetails,
599    /// For V1 contract update and init transactions, the return value.
600    pub return_value: Option<Vec<u8>>,
601}
602
603impl TryFrom<Option<Result<generated::DryRunResponse, tonic::Status>>>
604    for WithRemainingQuota<TransactionExecuted>
605{
606    type Error = DryRunError;
607
608    fn try_from(
609        value: Option<Result<generated::DryRunResponse, tonic::Status>>,
610    ) -> Result<Self, Self::Error> {
611        let response =
612            value.ok_or_else(|| tonic::Status::cancelled("server closed dry run stream"))??;
613        let quota_remaining = response.quota_remaining.require()?.into();
614        use generated::dry_run_response::*;
615        match response.response.require()? {
616            Response::Error(e) => {
617                let result = e.error.require()?.try_into()?;
618                if !matches!(
619                    result,
620                    ErrorResult::NoState
621                        | ErrorResult::AccountNotFound
622                        | ErrorResult::BalanceInsufficient { .. }
623                        | ErrorResult::EnergyInsufficient { .. }
624                ) {
625                    Err(tonic::Status::unknown("unexpected error response type"))?
626                }
627                Err(DryRunError::OperationFailed {
628                    result,
629                    quota_remaining,
630                })
631            }
632            Response::Success(s) => {
633                let response = s.response.require()?;
634                match response {
635                    generated::dry_run_success_response::Response::TransactionExecuted(res) => {
636                        let inner = TransactionExecuted {
637                            energy_cost: res.energy_cost.require()?.into(),
638                            details: res.details.require()?.try_into()?,
639                            return_value: res.return_value,
640                        };
641                        Ok(WithRemainingQuota {
642                            inner,
643                            quota_remaining,
644                        })
645                    }
646                    _ => Err(tonic::Status::unknown("unexpected success response type"))?,
647                }
648            }
649        }
650    }
651}
652
653pub type DryRunResult<T> = Result<WithRemainingQuota<T>, DryRunError>;
654
655/// A dry-run session.
656///
657/// The operations available in two variants, with and without the `begin_`
658/// prefix. The variants without a prefix will send the request and wait for the
659/// result when `await`ed. This is typically the simplest to use.
660/// The variants with the `begin_` prefix send the request when `await`ed,
661/// returning a future that can be `await`ed to retrieve the result.
662/// (This can be used to front-load operations where queries do not depend on
663/// the results of previous queries. This may be more efficient in high-latency
664/// situations.)
665///
666/// Before any other operations, [`DryRun::load_block_state`] (or
667/// [`DryRun::begin_load_block_state`]) should be called to ensure a block state
668/// is loaded. If it is not (or if loading the block state fails - for instance
669/// if an invalid block hash is supplied), other operations will result in an
670/// [`ErrorResult::NoState`] error.
671pub struct DryRun {
672    /// The channel used for sending requests to the server.
673    /// This is `None` if the session has been closed.
674    request_send: Option<channel::mpsc::Sender<generated::DryRunRequest>>,
675    /// The channel used for receiving responses from the server.
676    response_recv: shared_receiver::SharedReceiver<tonic::Result<generated::DryRunResponse>>,
677    /// The timeout in milliseconds for the dry-run session to complete.
678    timeout: u64,
679    /// The energy quota for the dry-run session as a whole.
680    energy_quota: u64,
681}
682
683impl DryRun {
684    /// Start a new dry-run session.
685    /// This may return `UNIMPLEMENTED` if the endpoint is not available on the
686    /// server. It may return `UNAVAILABLE` if the endpoint is not currently
687    /// available to due resource limitations.
688    pub(crate) async fn new(
689        client: &mut generated::queries_client::QueriesClient<tonic::transport::Channel>,
690    ) -> tonic::Result<Self> {
691        let (request_send, request_recv) = channel::mpsc::channel(10);
692        let response = client.dry_run(request_recv).await?;
693        let parse_meta_u64 = |key| response.metadata().get(key)?.to_str().ok()?.parse().ok();
694        let timeout: u64 = parse_meta_u64("timeout").ok_or_else(|| {
695            tonic::Status::internal("timeout metadata could not be parsed from server response")
696        })?;
697        let energy_quota: u64 = parse_meta_u64("quota").ok_or_else(|| {
698            tonic::Status::internal(
699                "energy quota metadata could not be parsed from server response",
700            )
701        })?;
702        let response_stream = response.into_inner();
703        let response_recv =
704            shared_receiver::SharedReceiver::new(futures::stream::StreamExt::fuse(response_stream));
705        Ok(DryRun {
706            request_send: Some(request_send),
707            response_recv,
708            timeout,
709            energy_quota,
710        })
711    }
712
713    /// Get the timeout for the dry-run session set by the server.
714    /// Returns `None` if the initial metadata did not include the timeout, or
715    /// it could not be parsed.
716    pub fn timeout(&self) -> std::time::Duration {
717        std::time::Duration::from_millis(self.timeout)
718    }
719
720    /// Get the total energy quota set for the dry-run session.
721    pub fn energy_quota(&self) -> Energy {
722        self.energy_quota.into()
723    }
724
725    /// Load the state from a specified block.
726    /// This can result in an error if the dry-run session has already been
727    /// closed, either by [`DryRun::close`] or by the server closing the
728    /// session. In this case, the response code indicates the cause.
729    /// If successful, this returns a future that can be used to wait for the
730    /// result of the operation. The following results are possible:
731    ///
732    ///  * [`BlockStateLoaded`] if the operation is successful.
733    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
734    ///    the following results:
735    ///    - [`ErrorResult::BlockNotFound`] if the block could not be found.
736    ///  * [`DryRunError::CallError`] if the server produced an error code, or
737    ///    if the server's response was unexpected.
738    ///    - If the server's response could not be interpreted, the result code
739    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
740    ///    - If the execution of the query would exceed the energy quota,
741    ///      `RESOURCE_EXHAUSTED` is returned.
742    ///    - If the timeout for the dry-run session has expired,
743    ///      `DEADLINE_EXCEEDED` is returned.
744    ///
745    /// The energy cost of this operation is 2000.
746    pub async fn begin_load_block_state(
747        &mut self,
748        bi: impl IntoBlockIdentifier,
749    ) -> tonic::Result<impl Future<Output = DryRunResult<BlockStateLoaded>>> {
750        let request = generated::DryRunRequest {
751            request: Some(dry_run_request::Request::LoadBlockState(
752                (&bi.into_block_identifier()).into(),
753            )),
754        };
755        Ok(self.request(request).await?.map(|z| z.try_into()))
756    }
757
758    /// Load the state from a specified block.
759    /// The following results are possible:
760    ///
761    ///  * [`DryRunError::CallError`] if the dry-run session has already been
762    ///    closed, either by [`DryRun::close`] or by the server closing the
763    ///    session. In this case, the response code indicates the cause.
764    ///  * [`BlockStateLoaded`] if the operation is successful.
765    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
766    ///    the following results:
767    ///    - [`ErrorResult::BlockNotFound`] if the block could not be found.
768    ///  * [`DryRunError::CallError`] if the server produced an error code, or
769    ///    if the server's response was unexpected.
770    ///    - If the server's response could not be interpreted, the result code
771    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
772    ///    - If the execution of the query would exceed the energy quota,
773    ///      `RESOURCE_EXHAUSTED` is returned.
774    ///    - If the timeout for the dry-run session has expired,
775    ///      `DEADLINE_EXCEEDED` is returned.
776    ///
777    /// The energy cost of this operation is 2000.
778    pub async fn load_block_state(
779        &mut self,
780        bi: impl IntoBlockIdentifier,
781    ) -> DryRunResult<BlockStateLoaded> {
782        self.begin_load_block_state(bi).await?.await
783    }
784
785    /// Get the account information for a specified account in the current
786    /// state. This can result in an error if the dry-run session has
787    /// already been closed, either by [`DryRun::close`] or by the server
788    /// closing the session. In this case, the response code indicates the
789    /// cause. If successful, this returns a future that can be used to wait
790    /// for the result of the operation. The following results are possible:
791    ///
792    ///  * [`AccountInfo`] if the operation is successful.
793    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
794    ///    the following results:
795    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
796    ///    - [`ErrorResult::AccountNotFound`] if the account could not be found.
797    ///  * [`DryRunError::CallError`] if the server produced an error code, or
798    ///    if the server's response was unexpected.
799    ///    - If the server's response could not be interpreted, the result code
800    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
801    ///    - If the execution of the query would exceed the energy quota,
802    ///      `RESOURCE_EXHAUSTED` is returned.
803    ///    - If the timeout for the dry-run session has expired,
804    ///      `DEADLINE_EXCEEDED` is returned.
805    ///
806    /// The energy cost of this operation is 200.
807    pub async fn begin_get_account_info(
808        &mut self,
809        acc: &AccountIdentifier,
810    ) -> tonic::Result<impl Future<Output = DryRunResult<AccountInfo>>> {
811        let request = generated::DryRunRequest {
812            request: Some(dry_run_request::Request::StateQuery(DryRunStateQuery {
813                query: Some(generated::dry_run_state_query::Query::GetAccountInfo(
814                    acc.into(),
815                )),
816            })),
817        };
818        Ok(self.request(request).await?.map(|z| z.try_into()))
819    }
820
821    /// Get the account information for a specified account in the current
822    /// state. The following results are possible:
823    ///
824    ///  * [`DryRunError::CallError`] if the dry-run session has already been
825    ///    closed, either by [`DryRun::close`] or by the server closing the
826    ///    session. In this case, the response code indicates the cause.
827    ///  * [`AccountInfo`] if the operation is successful.
828    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
829    ///    the following results:
830    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
831    ///    - [`ErrorResult::AccountNotFound`] if the account could not be found.
832    ///  * [`DryRunError::CallError`] if the server produced an error code, or
833    ///    if the server's response was unexpected.
834    ///    - If the server's response could not be interpreted, the result code
835    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
836    ///    - If the execution of the query would exceed the energy quota,
837    ///      `RESOURCE_EXHAUSTED` is returned.
838    ///    - If the timeout for the dry-run session has expired,
839    ///      `DEADLINE_EXCEEDED` is returned.
840    ///
841    /// The energy cost of this operation is 200.
842    pub async fn get_account_info(&mut self, acc: &AccountIdentifier) -> DryRunResult<AccountInfo> {
843        self.begin_get_account_info(acc).await?.await
844    }
845
846    /// Get the details of a specified smart contract instance in the current
847    /// state. This operation can result in an error if the dry-run session has
848    /// already been closed, either by [`DryRun::close`] or by the server
849    /// closing the session. In this case, the response code indicates the
850    /// cause. If successful, this returns a future that can be used to wait
851    /// for the result of the operation. The following results are possible:
852    ///
853    ///  * [`InstanceInfo`] if the operation is successful.
854    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
855    ///    the following results:
856    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
857    ///    - [`ErrorResult::AccountNotFound`] if the account could not be found.
858    ///  * [`DryRunError::CallError`] if the server produced an error code, or
859    ///    if the server's response was unexpected.
860    ///    - If the server's response could not be interpreted, the result code
861    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
862    ///    - If the execution of the query would exceed the energy quota,
863    ///      `RESOURCE_EXHAUSTED` is returned.
864    ///    - If the timeout for the dry-run session has expired,
865    ///      `DEADLINE_EXCEEDED` is returned.
866    ///
867    /// The energy cost of this operation is 200.
868    pub async fn begin_get_instance_info(
869        &mut self,
870        address: &ContractAddress,
871    ) -> tonic::Result<impl Future<Output = DryRunResult<InstanceInfo>>> {
872        let request = generated::DryRunRequest {
873            request: Some(dry_run_request::Request::StateQuery(DryRunStateQuery {
874                query: Some(generated::dry_run_state_query::Query::GetInstanceInfo(
875                    address.into(),
876                )),
877            })),
878        };
879        Ok(self.request(request).await?.map(|z| z.try_into()))
880    }
881
882    /// Get the details of a specified smart contract instance in the current
883    /// state. The following results are possible:
884    ///
885    ///  * [`DryRunError::CallError`] if the dry-run session has already been
886    ///    closed, either by [`DryRun::close`] or by the server closing the
887    ///    session. In this case, the response code indicates the cause.
888    ///  * [`InstanceInfo`] if the operation is successful.
889    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
890    ///    the following results:
891    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
892    ///    - [`ErrorResult::AccountNotFound`] if the account could not be found.
893    ///  * [`DryRunError::CallError`] if the server produced an error code, or
894    ///    if the server's response was unexpected.
895    ///    - If the server's response could not be interpreted, the result code
896    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
897    ///    - If the execution of the query would exceed the energy quota,
898    ///      `RESOURCE_EXHAUSTED` is returned.
899    ///    - If the timeout for the dry-run session has expired,
900    ///      `DEADLINE_EXCEEDED` is returned.
901    ///
902    /// The energy cost of this operation is 200.
903    pub async fn get_instance_info(
904        &mut self,
905        address: &ContractAddress,
906    ) -> DryRunResult<InstanceInfo> {
907        self.begin_get_instance_info(address).await?.await
908    }
909
910    /// Invoke an entrypoint on a smart contract instance in the current state.
911    /// Any changes this would make to the state will be rolled back so they are
912    /// not observable by subsequent operations in the dry-run session. (To make
913    /// updates that are observable within the dry-run session, use
914    /// [`DryRun::run_transaction`] instead.) This operation can result in an
915    /// error if the dry-run session has already been closed, either by
916    /// [`DryRun::close`] or by the server closing the session. In this case,
917    /// the response code indicates the cause. If successful, this returns a
918    /// future that can be used to wait for the result of the operation. The
919    /// following results are possible:
920    ///
921    ///  * [`InvokeInstanceSuccess`] if the operation is successful.
922    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
923    ///    the following results:
924    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
925    ///    - [`ErrorResult::InvokeFailure`] if the invocation failed. (This can
926    ///      be because the contract logic produced a reject, or a number of
927    ///      other reasons, such as the endpoint not existing.)
928    ///  * [`DryRunError::CallError`] if the server produced an error code, or
929    ///    if the server's response was unexpected.
930    ///    - If the server's response could not be interpreted, the result code
931    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
932    ///    - If the execution of the query would exceed the energy quota,
933    ///      `RESOURCE_EXHAUSTED` is returned.
934    ///    - If the timeout for the dry-run session has expired,
935    ///      `DEADLINE_EXCEEDED` is returned.
936    ///
937    /// The energy cost of this operation is 200 plus the energy used by the
938    /// execution of the contract endpoint.
939    pub async fn begin_invoke_instance(
940        &mut self,
941        context: &ContractContext,
942    ) -> tonic::Result<impl Future<Output = DryRunResult<InvokeInstanceSuccess>>> {
943        let request = generated::DryRunRequest {
944            request: Some(dry_run_request::Request::StateQuery(DryRunStateQuery {
945                query: Some(generated::dry_run_state_query::Query::InvokeInstance(
946                    context.into(),
947                )),
948            })),
949        };
950        Ok(self.request(request).await?.map(|z| z.try_into()))
951    }
952
953    /// Invoke an entrypoint on a smart contract instance in the current state.
954    /// Any changes this would make to the state will be rolled back so they are
955    /// not observable by subsequent operations in the dry-run session. (To make
956    /// updates that are observable within the dry-run session, use
957    /// [`DryRun::run_transaction`] instead.) The following results are
958    /// possible:
959    ///
960    ///  * [`DryRunError::CallError`] if the dry-run session has already been
961    ///    closed, either by [`DryRun::close`] or by the server closing the
962    ///    session. In this case, the response code indicates the cause.
963    ///  * [`InvokeInstanceSuccess`] if the operation is successful.
964    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
965    ///    the following results:
966    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
967    ///    - [`ErrorResult::InvokeFailure`] if the invocation failed. (This can
968    ///      be because the contract logic produced a reject, or a number of
969    ///      other reasons, such as the endpoint not existing.)
970    ///  * [`DryRunError::CallError`] if the server produced an error code, or
971    ///    if the server's response was unexpected.
972    ///    - If the server's response could not be interpreted, the result code
973    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
974    ///    - If the execution of the query would exceed the energy quota,
975    ///      `RESOURCE_EXHAUSTED` is returned.
976    ///    - If the timeout for the dry-run session has expired,
977    ///      `DEADLINE_EXCEEDED` is returned.
978    ///
979    /// The energy cost of this operation is 200 plus the energy used by the
980    /// execution of the contract endpoint.
981    pub async fn invoke_instance(
982        &mut self,
983        context: &ContractContext,
984    ) -> DryRunResult<InvokeInstanceSuccess> {
985        self.begin_invoke_instance(context).await?.await
986    }
987
988    /// Update the current timestamp for subsequent dry-run operations. The
989    /// timestamp is automatically set to the timestamp of the block loaded
990    /// by [`DryRun::load_block_state`]. For smart contracts that are time
991    /// sensitive, overriding the timestamp can be useful. This operation can
992    /// result in an error if the dry-run session has already been closed,
993    /// either by [`DryRun::close`] or by the server closing the session. In
994    /// this case, the response code indicates the cause. If successful,
995    /// this returns a future that can be used to wait for the result of the
996    /// operation. The following results are possible:
997    ///
998    ///  * [`TimestampSet`] if the operation is successful.
999    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
1000    ///    the following results:
1001    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
1002    ///  * [`DryRunError::CallError`] if the server produced an error code, or
1003    ///    if the server's response was unexpected.
1004    ///    - If the server's response could not be interpreted, the result code
1005    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
1006    ///    - If the execution of the query would exceed the energy quota,
1007    ///      `RESOURCE_EXHAUSTED` is returned.
1008    ///    - If the timeout for the dry-run session has expired,
1009    ///      `DEADLINE_EXCEEDED` is returned.
1010    ///
1011    /// The energy cost of this operation is 50.
1012    pub async fn begin_set_timestamp(
1013        &mut self,
1014        timestamp: Timestamp,
1015    ) -> tonic::Result<impl Future<Output = DryRunResult<TimestampSet>>> {
1016        let request = generated::DryRunRequest {
1017            request: Some(dry_run_request::Request::StateOperation(
1018                DryRunStateOperation {
1019                    operation: Some(generated::dry_run_state_operation::Operation::SetTimestamp(
1020                        timestamp.into(),
1021                    )),
1022                },
1023            )),
1024        };
1025        Ok(self.request(request).await?.map(|z| z.try_into()))
1026    }
1027
1028    /// Update the current timestamp for subsequent dry-run operations. The
1029    /// timestamp is automatically set to the timestamp of the block loaded
1030    /// by [`DryRun::load_block_state`]. For smart contracts that are time
1031    /// sensitive, overriding the timestamp can be useful. The following results
1032    /// are possible:
1033    ///
1034    ///  * [`DryRunError::CallError`] if the dry-run session has already been
1035    ///    closed, either by [`DryRun::close`] or by the server closing the
1036    ///    session. In this case, the response code indicates the cause.
1037    ///  * [`TimestampSet`] if the operation is successful.
1038    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
1039    ///    the following results:
1040    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
1041    ///  * [`DryRunError::CallError`] if the server produced an error code, or
1042    ///    if the server's response was unexpected.
1043    ///    - If the server's response could not be interpreted, the result code
1044    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
1045    ///    - If the execution of the query would exceed the energy quota,
1046    ///      `RESOURCE_EXHAUSTED` is returned.
1047    ///    - If the timeout for the dry-run session has expired,
1048    ///      `DEADLINE_EXCEEDED` is returned.
1049    ///
1050    /// The energy cost of this operation is 50.
1051    pub async fn set_timestamp(&mut self, timestamp: Timestamp) -> DryRunResult<TimestampSet> {
1052        self.begin_set_timestamp(timestamp).await?.await
1053    }
1054
1055    /// Mint a specified amount and award it to a specified account. This
1056    /// operation can result in an error if the dry-run session has already
1057    /// been closed, either by [`DryRun::close`] or by the server closing the
1058    /// session. In this case, the response code indicates the cause. If
1059    /// successful, this returns a future that can be used to wait for the
1060    /// result of the operation. The following results are possible:
1061    ///
1062    ///  * [`MintedToAccount`] if the operation is successful.
1063    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
1064    ///    the following results:
1065    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
1066    ///    - [`ErrorResult::AmountOverLimit`] if the minted amount would
1067    ///      overflow the total CCD supply.
1068    ///  * [`DryRunError::CallError`] if the server produced an error code, or
1069    ///    if the server's response was unexpected.
1070    ///    - If the server's response could not be interpreted, the result code
1071    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
1072    ///    - If the execution of the query would exceed the energy quota,
1073    ///      `RESOURCE_EXHAUSTED` is returned.
1074    ///    - If the timeout for the dry-run session has expired,
1075    ///      `DEADLINE_EXCEEDED` is returned.
1076    ///
1077    /// The energy cost of this operation is 400.
1078    pub async fn begin_mint_to_account(
1079        &mut self,
1080        account_address: &AccountAddress,
1081        mint_amount: Amount,
1082    ) -> tonic::Result<impl Future<Output = DryRunResult<MintedToAccount>>> {
1083        let request = generated::DryRunRequest {
1084            request: Some(dry_run_request::Request::StateOperation(
1085                DryRunStateOperation {
1086                    operation: Some(
1087                        generated::dry_run_state_operation::Operation::MintToAccount(
1088                            DryRunMintToAccount {
1089                                account: Some(account_address.into()),
1090                                amount: Some(mint_amount.into()),
1091                            },
1092                        ),
1093                    ),
1094                },
1095            )),
1096        };
1097        Ok(self.request(request).await?.map(|z| z.try_into()))
1098    }
1099
1100    /// Mint a specified amount and award it to a specified account. The
1101    /// following results are possible:
1102    ///
1103    ///  * [`DryRunError::CallError`] if the dry-run session has already been
1104    ///    closed, either by [`DryRun::close`] or by the server closing the
1105    ///    session. In this case, the response code indicates the cause.
1106    ///  * [`MintedToAccount`] if the operation is successful.
1107    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
1108    ///    the following results:
1109    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
1110    ///    - [`ErrorResult::AmountOverLimit`] if the minted amount would
1111    ///      overflow the total CCD supply.
1112    ///  * [`DryRunError::CallError`] if the server produced an error code, or
1113    ///    if the server's response was unexpected.
1114    ///    - If the server's response could not be interpreted, the result code
1115    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
1116    ///    - If the execution of the query would exceed the energy quota,
1117    ///      `RESOURCE_EXHAUSTED` is returned.
1118    ///    - If the timeout for the dry-run session has expired,
1119    ///      `DEADLINE_EXCEEDED` is returned.
1120    ///
1121    /// The energy cost of this operation is 400.
1122    pub async fn mint_to_account(
1123        &mut self,
1124        account_address: &AccountAddress,
1125        mint_amount: Amount,
1126    ) -> DryRunResult<MintedToAccount> {
1127        self.begin_mint_to_account(account_address, mint_amount)
1128            .await?
1129            .await
1130    }
1131
1132    /// Dry-run a transaction, updating the state of the dry-run session
1133    /// accordingly. This operation can result in an error if the dry-run
1134    /// session has already been closed, either by [`DryRun::close`] or by the
1135    /// server closing the session. In this case, the response code
1136    /// indicates the cause. If successful, this returns a future that can
1137    /// be used to wait for the result of the operation. The following
1138    /// results are possible:
1139    ///
1140    ///  * [`TransactionExecuted`] if the transaction was executed. This case
1141    ///    applies both if the transaction is rejected or successful.
1142    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
1143    ///    the following results:
1144    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
1145    ///    - [`ErrorResult::AccountNotFound`] if the sender account does not
1146    ///      exist.
1147    ///    - [`ErrorResult::BalanceInsufficient`] if the sender account does not
1148    ///      have sufficient balance to pay the deposit for the transaction.
1149    ///    - [`ErrorResult::EnergyInsufficient`] if the specified energy is not
1150    ///      sufficient to cover the cost of the basic checks required for a
1151    ///      transaction to be included in the chain.
1152    ///  * [`DryRunError::CallError`] if the server produced an error code, or
1153    ///    if the server's response was unexpected.
1154    ///    - If the server's response could not be interpreted, the result code
1155    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
1156    ///    - If the execution of the query would exceed the energy quota,
1157    ///      `RESOURCE_EXHAUSTED` is returned.
1158    ///    - If the timeout for the dry-run session has expired,
1159    ///      `DEADLINE_EXCEEDED` is returned.
1160    ///
1161    /// The energy cost of this operation is 400.   
1162    pub async fn begin_run_transaction(
1163        &mut self,
1164        transaction: DryRunTransaction,
1165    ) -> tonic::Result<impl Future<Output = DryRunResult<TransactionExecuted>>> {
1166        let request = generated::DryRunRequest {
1167            request: Some(dry_run_request::Request::StateOperation(
1168                DryRunStateOperation {
1169                    operation: Some(
1170                        generated::dry_run_state_operation::Operation::RunTransaction(
1171                            transaction.into(),
1172                        ),
1173                    ),
1174                },
1175            )),
1176        };
1177        Ok(self.request(request).await?.map(|z| z.try_into()))
1178    }
1179
1180    /// Dry-run a transaction, updating the state of the dry-run session
1181    /// accordingly. The following results are possible:
1182    ///
1183    ///  * [`DryRunError::CallError`] if the dry-run session has already been
1184    ///    closed, either by [`DryRun::close`] or by the server closing the
1185    ///    session. In this case, the response code indicates the cause.
1186    ///  * [`TransactionExecuted`] if the transaction was executed. This case
1187    ///    applies both if the transaction is rejected or successful.
1188    ///  * [`DryRunError::OperationFailed`] if the operation failed, with one of
1189    ///    the following results:
1190    ///    - [`ErrorResult::NoState`] if no block state has been loaded.
1191    ///    - [`ErrorResult::AccountNotFound`] if the sender account does not
1192    ///      exist.
1193    ///    - [`ErrorResult::BalanceInsufficient`] if the sender account does not
1194    ///      have sufficient balance to pay the deposit for the transaction.
1195    ///    - [`ErrorResult::EnergyInsufficient`] if the specified energy is not
1196    ///      sufficient to cover the cost of the basic checks required for a
1197    ///      transaction to be included in the chain.
1198    ///  * [`DryRunError::CallError`] if the server produced an error code, or
1199    ///    if the server's response was unexpected.
1200    ///    - If the server's response could not be interpreted, the result code
1201    ///      `INVALID_ARGUMENT` or `UNKNOWN` is returned.
1202    ///    - If the execution of the query would exceed the energy quota,
1203    ///      `RESOURCE_EXHAUSTED` is returned.
1204    ///    - If the timeout for the dry-run session has expired,
1205    ///      `DEADLINE_EXCEEDED` is returned.
1206    ///
1207    /// The energy cost of this operation is 400.   
1208    pub async fn run_transaction(
1209        &mut self,
1210        transaction: DryRunTransaction,
1211    ) -> DryRunResult<TransactionExecuted> {
1212        self.begin_run_transaction(transaction).await?.await
1213    }
1214
1215    /// Close the request stream. Any subsequent dry-run requests will result in
1216    /// a `CANCELLED` status code. Closing the request stream allows the
1217    /// server to free resources associated with the dry-run session. It is
1218    /// recommended to close the request stream if the [`DryRun`] object will
1219    /// be retained for any significant length of time after the last request is
1220    /// made.
1221    ///
1222    /// Note that dropping the [`DryRun`] object will stop the background task
1223    /// that services in-flight requests, so it should not be dropped before
1224    /// `await`ing any such requests. Closing the request stream does not stop
1225    /// the background task.
1226    pub fn close(&mut self) {
1227        self.request_send = None;
1228    }
1229
1230    /// Helper function that issues a dry-run request and returns a future for
1231    /// the corresponding response.
1232    async fn request(
1233        &mut self,
1234        request: generated::DryRunRequest,
1235    ) -> tonic::Result<impl Future<Output = Option<tonic::Result<generated::DryRunResponse>>>> {
1236        let lazy_cancelled = || tonic::Status::cancelled("dry run already completed");
1237        let sender = self.request_send.as_mut().ok_or_else(lazy_cancelled)?;
1238        let send_result = sender.send(request).await;
1239        let receive_result = self.response_recv.next().ok_or_else(lazy_cancelled);
1240        match send_result {
1241            Ok(_) => receive_result.map(|r| r.map(|x| x.ok())),
1242            Err(_) => {
1243                // In this case, the server must have closed the stream. We query the response
1244                // stream to see if there is an error indicating the reason.
1245                if let Ok(Err(e)) = receive_result?.await {
1246                    Err(e)
1247                } else {
1248                    Err(lazy_cancelled())
1249                }
1250            }
1251        }
1252    }
1253}