sui_rpc_api/grpc/v2/transaction_execution_service/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::ErrorReason;
5use crate::RpcError;
6use crate::RpcService;
7use prost_types::FieldMask;
8use sui_rpc::field::FieldMaskTree;
9use sui_rpc::field::FieldMaskUtil;
10use sui_rpc::merge::Merge;
11use sui_rpc::proto::google::rpc::bad_request::FieldViolation;
12use sui_rpc::proto::sui::rpc::v2::ExecuteTransactionRequest;
13use sui_rpc::proto::sui::rpc::v2::ExecuteTransactionResponse;
14use sui_rpc::proto::sui::rpc::v2::ExecutedTransaction;
15use sui_rpc::proto::sui::rpc::v2::ObjectSet;
16use sui_rpc::proto::sui::rpc::v2::SimulateTransactionRequest;
17use sui_rpc::proto::sui::rpc::v2::SimulateTransactionResponse;
18use sui_rpc::proto::sui::rpc::v2::Transaction;
19use sui_rpc::proto::sui::rpc::v2::UserSignature;
20use sui_rpc::proto::sui::rpc::v2::transaction_execution_service_server::TransactionExecutionService;
21use sui_types::balance_change::derive_balance_changes_2;
22use sui_types::transaction_executor::TransactionExecutor;
23use tap::Pipe;
24
25mod simulate;
26
27#[tonic::async_trait]
28impl TransactionExecutionService for RpcService {
29    async fn execute_transaction(
30        &self,
31        request: tonic::Request<ExecuteTransactionRequest>,
32    ) -> Result<tonic::Response<ExecuteTransactionResponse>, tonic::Status> {
33        let executor = self
34            .executor
35            .as_ref()
36            .ok_or_else(|| tonic::Status::unimplemented("no transaction executor"))?;
37
38        execute_transaction(self, executor, request.into_inner())
39            .await
40            .map(tonic::Response::new)
41            .map_err(Into::into)
42    }
43
44    async fn simulate_transaction(
45        &self,
46        request: tonic::Request<SimulateTransactionRequest>,
47    ) -> Result<tonic::Response<SimulateTransactionResponse>, tonic::Status> {
48        simulate::simulate_transaction(self, request.into_inner())
49            .map(tonic::Response::new)
50            .map_err(Into::into)
51    }
52}
53
54pub const EXECUTE_TRANSACTION_READ_MASK_DEFAULT: &str =
55    crate::read_mask_defaults::EXECUTE_TRANSACTION;
56// Current maximum number of supported UserSignature's,
57// one for the sender and one for an optional sponsor
58const MAX_NUMBER_OF_SIGNATURES: usize = 2;
59
60#[tracing::instrument(skip(service, executor))]
61pub async fn execute_transaction(
62    service: &RpcService,
63    executor: &std::sync::Arc<dyn TransactionExecutor>,
64    request: ExecuteTransactionRequest,
65) -> Result<ExecuteTransactionResponse, RpcError> {
66    let transaction = request
67        .transaction
68        .as_ref()
69        .ok_or_else(|| FieldViolation::new("transaction").with_reason(ErrorReason::FieldMissing))?
70        .pipe(sui_sdk_types::Transaction::try_from)
71        .map_err(|e| {
72            FieldViolation::new("transaction")
73                .with_description(format!("invalid transaction: {e}"))
74                .with_reason(ErrorReason::FieldInvalid)
75        })?;
76
77    if request.signatures.len() > MAX_NUMBER_OF_SIGNATURES {
78        return Err(FieldViolation::new("signatures")
79            .with_description(format!(
80                "{} provided signatures exceeds the maximum allowed of {}",
81                request.signatures.len(),
82                MAX_NUMBER_OF_SIGNATURES
83            ))
84            .with_reason(ErrorReason::FieldInvalid)
85            .into());
86    }
87
88    let signatures = request
89        .signatures
90        .iter()
91        .enumerate()
92        .map(|(i, signature)| {
93            sui_sdk_types::UserSignature::try_from(signature).map_err(|e| {
94                FieldViolation::new_at("signatures", i)
95                    .with_description(format!("invalid signature: {e}"))
96                    .with_reason(ErrorReason::FieldInvalid)
97            })
98        })
99        .collect::<Result<Vec<_>, _>>()?;
100
101    let signed_transaction = sui_sdk_types::SignedTransaction {
102        transaction: transaction.clone(),
103        signatures: signatures.clone(),
104    };
105
106    let read_mask = {
107        let read_mask = request
108            .read_mask
109            .unwrap_or_else(|| FieldMask::from_str(EXECUTE_TRANSACTION_READ_MASK_DEFAULT));
110        read_mask
111            .validate::<ExecutedTransaction>()
112            .map_err(|path| {
113                FieldViolation::new("read_mask")
114                    .with_description(format!("invalid read_mask path: {path}"))
115                    .with_reason(ErrorReason::FieldInvalid)
116            })?;
117        FieldMaskTree::from(read_mask)
118    };
119
120    let request = sui_types::transaction_driver_types::ExecuteTransactionRequestV3 {
121        transaction: signed_transaction.try_into()?,
122        include_events: read_mask.contains(ExecutedTransaction::EVENTS_FIELD.name),
123        include_input_objects: read_mask.contains(ExecutedTransaction::BALANCE_CHANGES_FIELD.name)
124            || read_mask.contains(ExecutedTransaction::OBJECTS_FIELD.name)
125            || read_mask.contains(ExecutedTransaction::EFFECTS_FIELD.name),
126        include_output_objects: read_mask.contains(ExecutedTransaction::BALANCE_CHANGES_FIELD.name)
127            || read_mask.contains(ExecutedTransaction::OBJECTS_FIELD.name)
128            || read_mask.contains(ExecutedTransaction::EFFECTS_FIELD.name),
129        include_auxiliary_data: false,
130    };
131
132    let sui_types::transaction_driver_types::ExecuteTransactionResponseV3 {
133        effects:
134            sui_types::transaction_driver_types::FinalizedEffects {
135                effects,
136                finality_info: _,
137            },
138        events,
139        input_objects,
140        output_objects,
141        auxiliary_data: _,
142    } = executor.execute_transaction(request, None).await?;
143
144    let executed_transaction = {
145        // Build the objects set first so we can use it for event JSON rendering.
146        // This allows resolving types from packages that were just published in this transaction.
147        let objects = {
148            let mut objects = sui_types::full_checkpoint_content::ObjectSet::default();
149            for o in input_objects
150                .into_iter()
151                .chain(output_objects.into_iter())
152                .flatten()
153            {
154                objects.insert(o);
155            }
156            objects
157        };
158
159        let events = read_mask
160            .subtree(ExecutedTransaction::EVENTS_FIELD)
161            .and_then(|mask| {
162                events.map(|events| service.render_events_to_proto(&events, &mask, &objects))
163            });
164
165        let balance_changes = if read_mask.contains(ExecutedTransaction::BALANCE_CHANGES_FIELD) {
166            derive_balance_changes_2(&effects, &objects)
167                .into_iter()
168                .map(Into::into)
169                .collect()
170        } else {
171            vec![]
172        };
173
174        let effects = read_mask
175            .subtree(ExecutedTransaction::EFFECTS_FIELD)
176            .map(|mask| service.render_effects_to_proto(&effects, &[], &objects, &mask));
177
178        let mut message = ExecutedTransaction::default();
179        message.digest = read_mask
180            .contains(ExecutedTransaction::DIGEST_FIELD)
181            .then(|| transaction.digest().to_string());
182        message.transaction = read_mask
183            .subtree(ExecutedTransaction::TRANSACTION_FIELD)
184            .map(|mask| Transaction::merge_from(transaction, &mask));
185        message.signatures = read_mask
186            .subtree(ExecutedTransaction::SIGNATURES_FIELD)
187            .map(|mask| {
188                signatures
189                    .into_iter()
190                    .map(|s| UserSignature::merge_from(s, &mask))
191                    .collect()
192            })
193            .unwrap_or_default();
194        message.effects = effects;
195        message.events = events;
196        message.balance_changes = balance_changes;
197        message.objects = read_mask
198            .subtree(
199                ExecutedTransaction::path_builder()
200                    .objects()
201                    .objects()
202                    .finish(),
203            )
204            .map(|mask| {
205                ObjectSet::default().with_objects(
206                    objects
207                        .iter()
208                        .map(|o| service.render_object_to_proto(o, &mask, &objects))
209                        .collect(),
210                )
211            });
212        message
213    };
214
215    Ok(ExecuteTransactionResponse::default().with_transaction(executed_transaction))
216}