1use futures::FutureExt;
5use mysten_common::ZipDebugEqIteratorExt;
6use prost_types::Struct;
7use sui_rpc::{
8 field::FieldMaskTree,
9 merge::Merge,
10 proto::sui::rpc::v2::{
11 Bcs, Display, Event, ExecutedTransaction, Object, TransactionEffects, TransactionEvents,
12 },
13};
14use sui_types::full_checkpoint_content::ObjectSet;
15
16use crate::{RpcService, reader::DisplayStore};
17
18impl RpcService {
19 pub fn render_object_to_proto(
20 &self,
21 object: &sui_types::object::Object,
22 read_mask: &FieldMaskTree,
23 output_objects: &ObjectSet,
24 ) -> Object {
25 let mut message = Object::default();
26
27 if read_mask.contains(Object::JSON_FIELD) {
28 let move_object = object.data.try_as_move();
29 message.json = move_object.and_then(|m| {
30 self.render_json(&m.type_().clone().into(), m.contents(), output_objects)
31 .map(Box::new)
32 });
33 }
34
35 if read_mask.contains(Object::DISPLAY_FIELD) {
36 message.display = self.render_object_display(object).map(Box::new);
37 }
38
39 message.merge(object, read_mask);
40
41 message
42 }
43
44 pub fn render_json(
47 &self,
48 struct_tag: &move_core_types::language_storage::StructTag,
49 contents: &[u8],
50 output_objects: &ObjectSet,
51 ) -> Option<prost_types::Value> {
52 let mut budget = self.config.max_json_move_value_size();
53 self.render_json_with_budget(struct_tag, contents, output_objects, &mut budget)
54 }
55
56 pub fn render_json_with_budget(
64 &self,
65 struct_tag: &move_core_types::language_storage::StructTag,
66 contents: &[u8],
67 output_objects: &ObjectSet,
68 size_budget: &mut usize,
69 ) -> Option<prost_types::Value> {
70 let layout = self
71 .reader
72 .inner()
73 .get_struct_layout_with_overlay(struct_tag, output_objects)
74 .ok()
75 .flatten()?;
76
77 sui_types::object::rpc_visitor::proto::ProtoVisitor::deserialize_value_with_budget(
78 contents,
79 &layout,
80 size_budget,
81 )
82 .map_err(|e| tracing::debug!("unable to convert move value to JSON: {e}"))
83 .ok()
84 }
85
86 pub fn render_object_display(&self, object: &sui_types::object::Object) -> Option<Display> {
87 let move_object = object.data.try_as_move()?;
88 let object_type = &move_object.type_().clone().into();
89 let contents = move_object.contents();
90
91 let limits = sui_display::v2::Limits {
92 max_depth: self.config.display().max_field_depth(),
93 max_nodes: self.config.display().max_format_nodes(),
94 max_loads: self.config.display().max_object_loads(),
95 };
96 let display_object = self.reader.get_display_object_v2_by_type(object_type)?;
97 let display_template =
98 sui_display::v2::Display::parse(limits, display_object.fields()).ok()?;
99
100 let layout = self
101 .reader
102 .inner()
103 .get_struct_layout(object_type)
104 .ok()
105 .flatten()?;
106
107 let root = sui_display::v2::OwnedSlice::new(layout, contents.to_owned());
108 let interpreter = sui_display::v2::Interpreter::new(root, DisplayStore::new(&self.reader));
109
110 let mut display = Display::default();
111
112 match display_template
116 .display::<prost_types::Value>(
117 self.config.display().max_move_value_depth(),
118 self.config.display().max_output_size(),
119 &interpreter,
120 )
121 .now_or_never()
122 .unwrap()
123 {
124 Ok(rendered) => {
125 let mut output = Struct::default();
126 let mut errors = Struct::default();
127
128 for (field, result) in rendered {
129 match result {
130 Ok(value) => {
131 output.fields.insert(field, value);
132 }
133 Err(e) => {
134 errors.fields.insert(field, e.to_string().into());
135 }
136 }
137 }
138
139 if !output.fields.is_empty() {
140 display.set_output(output.fields);
141 }
142
143 if !errors.fields.is_empty() {
144 display.set_errors(errors.fields);
145 }
146 }
147 Err(e) => {
148 display.set_errors(e.to_string());
149 }
150 }
151
152 Some(display)
153 }
154
155 pub fn render_events_to_proto(
156 &self,
157 events: &sui_types::effects::TransactionEvents,
158 mask: &FieldMaskTree,
159 output_objects: &ObjectSet,
160 ) -> TransactionEvents {
161 let mut message = TransactionEvents::default();
162
163 if mask.contains(TransactionEvents::BCS_FIELD) {
164 let mut bcs = Bcs::serialize(&events).unwrap();
165 bcs.name = Some("TransactionEvents".to_owned());
166 message.bcs = Some(bcs);
167 }
168
169 if mask.contains(TransactionEvents::DIGEST_FIELD) {
170 message.digest = Some(events.digest().to_string());
171 }
172
173 if let Some(event_mask) = mask.subtree(TransactionEvents::EVENTS_FIELD) {
174 message.events = events
175 .data
176 .iter()
177 .map(|event| self.render_event_to_proto(event, &event_mask, output_objects))
178 .collect();
179 }
180
181 message
182 }
183
184 pub fn render_event_to_proto(
185 &self,
186 event: &sui_types::event::Event,
187 mask: &FieldMaskTree,
188 output_objects: &ObjectSet,
189 ) -> Event {
190 let mut message = Event::default();
191
192 if mask.contains(Event::PACKAGE_ID_FIELD) {
193 message.set_package_id(event.package_id.to_canonical_string(true));
194 }
195
196 if mask.contains(Event::MODULE_FIELD) {
197 message.set_module(event.transaction_module.to_string());
198 }
199
200 if mask.contains(Event::SENDER_FIELD) {
201 message.sender = Some(event.sender.to_string());
202 }
203
204 if mask.contains(Event::EVENT_TYPE_FIELD) {
205 message.event_type = Some(event.type_.to_canonical_string(true));
206 }
207
208 if mask.contains(Event::CONTENTS_FIELD) {
209 let mut bcs = Bcs::from(event.contents.clone());
210 bcs.name = Some(event.type_.to_canonical_string(true));
211 message.contents = Some(bcs);
212 }
213
214 if mask.contains(Event::JSON_FIELD) {
215 message.json = self
216 .render_json(&event.type_, &event.contents, output_objects)
217 .map(Box::new);
218 }
219
220 message
221 }
222
223 pub fn render_clever_error(&self, effects: &mut TransactionEffects) {
225 use sui_rpc::proto::sui::rpc::v2::CleverError;
226 use sui_rpc::proto::sui::rpc::v2::MoveAbort;
227 use sui_rpc::proto::sui::rpc::v2::clever_error;
228 use sui_rpc::proto::sui::rpc::v2::execution_error::ErrorDetails;
229
230 let Some(move_abort) = effects
231 .status
232 .as_mut()
233 .and_then(|status| status.error.as_mut())
234 .and_then(|error| match &mut error.error_details {
235 Some(ErrorDetails::Abort(move_abort)) => Some(move_abort),
236 _ => None,
237 })
238 else {
239 return;
240 };
241
242 fn render(service: &RpcService, move_abort: &MoveAbort) -> Option<CleverError> {
243 let location = move_abort.location.as_ref()?;
244 let abort_code = move_abort.abort_code();
245 let package_id = location.package().parse::<sui_sdk_types::Address>().ok()?;
246 let module = location.module();
247
248 let package = {
249 let object = service.reader.inner().get_object(&package_id.into())?;
250 sui_package_resolver::Package::read_from_object(&object).ok()?
251 };
252
253 let clever_error = package.resolve_clever_error(module, abort_code)?;
254
255 let mut clever_error_message = CleverError::default();
256
257 match clever_error.error_info {
258 sui_package_resolver::ErrorConstants::None => {}
259 sui_package_resolver::ErrorConstants::Rendered {
260 identifier,
261 constant,
262 } => {
263 clever_error_message.constant_name = Some(identifier);
264 clever_error_message.value = Some(clever_error::Value::Rendered(constant));
265 }
266 sui_package_resolver::ErrorConstants::Raw { identifier, bytes } => {
267 clever_error_message.constant_name = Some(identifier);
268 clever_error_message.value = Some(clever_error::Value::Raw(bytes.into()));
269 }
270 }
271
272 clever_error_message.error_code = clever_error.error_code.map(Into::into);
273 clever_error_message.line_number = Some(clever_error.source_line_number.into());
274
275 Some(clever_error_message)
276 }
277
278 move_abort.clever_error = render(self, move_abort);
279 }
280
281 pub fn render_effects_to_proto(
282 &self,
283 effects: &sui_types::effects::TransactionEffects,
284 unchanged_loaded_runtime_objects: &[sui_types::storage::ObjectKey],
285 objects: &ObjectSet,
286 mask: &FieldMaskTree,
287 ) -> TransactionEffects {
288 let mut message = TransactionEffects::merge_from(effects, mask);
290
291 if mask.contains(TransactionEffects::UNCHANGED_LOADED_RUNTIME_OBJECTS_FIELD) {
292 message.unchanged_loaded_runtime_objects = unchanged_loaded_runtime_objects
293 .iter()
294 .map(Into::into)
295 .collect();
296 }
297
298 self.annotate_effects(&mut message, objects, mask);
299
300 message
301 }
302
303 pub fn annotate_effects(
307 &self,
308 effects: &mut TransactionEffects,
309 objects: &ObjectSet,
310 mask: &FieldMaskTree,
311 ) {
312 if mask.contains(TransactionEffects::CHANGED_OBJECTS_FIELD) {
313 for changed_object in effects.changed_objects.iter_mut() {
314 let Ok(object_id) = changed_object
315 .object_id()
316 .parse::<sui_types::base_types::ObjectID>()
317 else {
318 continue;
319 };
320
321 if let Some(object) = objects.get(&sui_types::storage::ObjectKey(
322 object_id,
323 changed_object
324 .input_version_opt()
325 .unwrap_or_else(|| changed_object.output_version())
326 .into(),
327 )) {
328 changed_object.set_object_type(object_type_to_string(object.into()));
329 }
330 }
331 }
332
333 if mask.contains(TransactionEffects::UNCHANGED_CONSENSUS_OBJECTS_FIELD) {
334 for unchanged_consensus_object in effects.unchanged_consensus_objects.iter_mut() {
335 let Ok(object_id) = unchanged_consensus_object
336 .object_id()
337 .parse::<sui_types::base_types::ObjectID>()
338 else {
339 continue;
340 };
341
342 if let Some(object) = objects.get(&sui_types::storage::ObjectKey(
343 object_id,
344 unchanged_consensus_object.version().into(),
345 )) {
346 unchanged_consensus_object
347 .set_object_type(object_type_to_string(object.into()));
348 }
349 }
350 }
351
352 self.render_clever_error(effects);
354 }
355
356 pub fn annotate_checkpoint_transaction(
362 &self,
363 message: &mut ExecutedTransaction,
364 transaction: &sui_types::full_checkpoint_content::ExecutedTransaction,
365 objects: &ObjectSet,
366 mask: &FieldMaskTree,
367 json_budget: &mut usize,
368 ) {
369 if let Some(effects_mask) = mask.subtree(ExecutedTransaction::EFFECTS_FIELD)
370 && let Some(effects) = message.effects.as_mut()
371 {
372 self.annotate_effects(effects, objects, &effects_mask);
373 }
374
375 if let Some(events_mask) = mask.subtree(ExecutedTransaction::EVENTS_FIELD)
376 && let Some(event_mask) = events_mask.subtree(TransactionEvents::EVENTS_FIELD)
377 && event_mask.contains(Event::JSON_FIELD)
378 && let Some(rendered) = message.events.as_mut()
379 && let Some(events) = &transaction.events
380 {
381 for (event_message, event) in rendered.events.iter_mut().zip_debug_eq(&events.data) {
382 event_message.json = self
383 .render_json_with_budget(&event.type_, &event.contents, objects, json_budget)
384 .map(Box::new);
385 }
386 }
387 }
388}
389
390fn object_type_to_string(object_type: sui_types::base_types::ObjectType) -> String {
391 match object_type {
392 sui_types::base_types::ObjectType::Package => "package".to_owned(),
393 sui_types::base_types::ObjectType::Struct(move_object_type) => {
394 move_object_type.to_canonical_string(true)
395 }
396 }
397}