sui_rpc_api/grpc/v2/transaction_execution_service/
mod.rs1use 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 = "effects";
55
56#[tracing::instrument(skip(service, executor))]
57pub async fn execute_transaction(
58 service: &RpcService,
59 executor: &std::sync::Arc<dyn TransactionExecutor>,
60 request: ExecuteTransactionRequest,
61) -> Result<ExecuteTransactionResponse, RpcError> {
62 let transaction = request
63 .transaction
64 .as_ref()
65 .ok_or_else(|| FieldViolation::new("transaction").with_reason(ErrorReason::FieldMissing))?
66 .pipe(sui_sdk_types::Transaction::try_from)
67 .map_err(|e| {
68 FieldViolation::new("transaction")
69 .with_description(format!("invalid transaction: {e}"))
70 .with_reason(ErrorReason::FieldInvalid)
71 })?;
72
73 let signatures = request
74 .signatures
75 .iter()
76 .enumerate()
77 .map(|(i, signature)| {
78 sui_sdk_types::UserSignature::try_from(signature).map_err(|e| {
79 FieldViolation::new_at("signatures", i)
80 .with_description(format!("invalid signature: {e}"))
81 .with_reason(ErrorReason::FieldInvalid)
82 })
83 })
84 .collect::<Result<Vec<_>, _>>()?;
85
86 let signed_transaction = sui_sdk_types::SignedTransaction {
87 transaction: transaction.clone(),
88 signatures: signatures.clone(),
89 };
90
91 let read_mask = {
92 let read_mask = request
93 .read_mask
94 .unwrap_or_else(|| FieldMask::from_str(EXECUTE_TRANSACTION_READ_MASK_DEFAULT));
95 read_mask
96 .validate::<ExecutedTransaction>()
97 .map_err(|path| {
98 FieldViolation::new("read_mask")
99 .with_description(format!("invalid read_mask path: {path}"))
100 .with_reason(ErrorReason::FieldInvalid)
101 })?;
102 FieldMaskTree::from(read_mask)
103 };
104
105 let request = sui_types::quorum_driver_types::ExecuteTransactionRequestV3 {
106 transaction: signed_transaction.try_into()?,
107 include_events: read_mask.contains(ExecutedTransaction::EVENTS_FIELD.name),
108 include_input_objects: read_mask.contains(ExecutedTransaction::BALANCE_CHANGES_FIELD.name)
109 || read_mask.contains(ExecutedTransaction::OBJECTS_FIELD.name)
110 || read_mask.contains(ExecutedTransaction::EFFECTS_FIELD.name),
111 include_output_objects: read_mask.contains(ExecutedTransaction::BALANCE_CHANGES_FIELD.name)
112 || read_mask.contains(ExecutedTransaction::OBJECTS_FIELD.name)
113 || read_mask.contains(ExecutedTransaction::EFFECTS_FIELD.name),
114 include_auxiliary_data: false,
115 };
116
117 let sui_types::quorum_driver_types::ExecuteTransactionResponseV3 {
118 effects:
119 sui_types::quorum_driver_types::FinalizedEffects {
120 effects,
121 finality_info: _,
122 },
123 events,
124 input_objects,
125 output_objects,
126 auxiliary_data: _,
127 } = executor.execute_transaction(request, None).await?;
128
129 let executed_transaction = {
130 let events = read_mask
131 .subtree(ExecutedTransaction::EVENTS_FIELD)
132 .and_then(|mask| events.map(|events| service.render_events_to_proto(&events, &mask)));
133
134 let objects = {
135 let mut objects = sui_types::full_checkpoint_content::ObjectSet::default();
136 for o in input_objects
137 .into_iter()
138 .chain(output_objects.into_iter())
139 .flatten()
140 {
141 objects.insert(o);
142 }
143 objects
144 };
145
146 let balance_changes = if read_mask.contains(ExecutedTransaction::BALANCE_CHANGES_FIELD) {
147 derive_balance_changes_2(&effects, &objects)
148 .into_iter()
149 .map(Into::into)
150 .collect()
151 } else {
152 vec![]
153 };
154
155 let effects = read_mask
156 .subtree(ExecutedTransaction::EFFECTS_FIELD)
157 .map(|mask| {
158 service.render_effects_to_proto(
159 &effects,
160 &[],
161 |object_id| {
162 objects
163 .iter()
164 .find(|o| o.id() == *object_id)
165 .map(|o| o.into())
166 },
167 &mask,
168 )
169 });
170
171 let mut message = ExecutedTransaction::default();
172 message.digest = read_mask
173 .contains(ExecutedTransaction::DIGEST_FIELD)
174 .then(|| transaction.digest().to_string());
175 message.transaction = read_mask
176 .subtree(ExecutedTransaction::TRANSACTION_FIELD)
177 .map(|mask| Transaction::merge_from(transaction, &mask));
178 message.signatures = read_mask
179 .subtree(ExecutedTransaction::SIGNATURES_FIELD)
180 .map(|mask| {
181 signatures
182 .into_iter()
183 .map(|s| UserSignature::merge_from(s, &mask))
184 .collect()
185 })
186 .unwrap_or_default();
187 message.effects = effects;
188 message.events = events;
189 message.balance_changes = balance_changes;
190 message.objects = read_mask
191 .subtree(
192 ExecutedTransaction::path_builder()
193 .objects()
194 .objects()
195 .finish(),
196 )
197 .map(|mask| {
198 ObjectSet::default().with_objects(
199 objects
200 .iter()
201 .map(|o| service.render_object_to_proto(o, &mask))
202 .collect(),
203 )
204 });
205 message
206 };
207
208 Ok(ExecuteTransactionResponse::default().with_transaction(executed_transaction))
209}