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}