sui_rpc_api/grpc/v2/
render.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use futures::FutureExt;
5use prost_types::Struct;
6use sui_rpc::{
7    field::FieldMaskTree,
8    merge::Merge,
9    proto::sui::rpc::v2::{Bcs, Display, Event, Object, TransactionEffects, TransactionEvents},
10};
11use sui_types::full_checkpoint_content::ObjectSet;
12
13use crate::{RpcService, reader::DisplayStore};
14
15impl RpcService {
16    pub fn render_object_to_proto(
17        &self,
18        object: &sui_types::object::Object,
19        read_mask: &FieldMaskTree,
20        output_objects: &ObjectSet,
21    ) -> Object {
22        let mut message = Object::default();
23
24        if read_mask.contains(Object::JSON_FIELD) {
25            let move_object = object.data.try_as_move();
26            message.json = move_object.and_then(|m| {
27                self.render_json(&m.type_().clone().into(), m.contents(), output_objects)
28                    .map(Box::new)
29            });
30        }
31
32        if read_mask.contains(Object::DISPLAY_FIELD) {
33            message.display = self.render_object_display(object).map(Box::new);
34        }
35
36        message.merge(object, read_mask);
37
38        message
39    }
40
41    /// Render a Move value as JSON.
42    /// If output_objects is provided, packages from it will be checked first before the backing store.
43    pub fn render_json(
44        &self,
45        struct_tag: &move_core_types::language_storage::StructTag,
46        contents: &[u8],
47        output_objects: &ObjectSet,
48    ) -> Option<prost_types::Value> {
49        let layout = self
50            .reader
51            .inner()
52            .get_struct_layout_with_overlay(struct_tag, output_objects)
53            .ok()
54            .flatten()?;
55
56        let bound = self.config.max_json_move_value_size();
57        sui_types::object::rpc_visitor::proto::ProtoVisitor::new(bound)
58            .deserialize_value(contents, &layout)
59            .map_err(|e| tracing::debug!("unable to convert move value to JSON: {e}"))
60            .ok()
61    }
62
63    pub fn render_object_display(&self, object: &sui_types::object::Object) -> Option<Display> {
64        let move_object = object.data.try_as_move()?;
65        let object_type = &move_object.type_().clone().into();
66        let contents = move_object.contents();
67
68        let limits = sui_display::v2::Limits {
69            max_depth: self.config.display().max_field_depth(),
70            max_nodes: self.config.display().max_format_nodes(),
71            max_loads: self.config.display().max_object_loads(),
72        };
73        let display_object = self.reader.get_display_object_v2_by_type(object_type)?;
74        let display_template =
75            sui_display::v2::Display::parse(limits, display_object.fields()).ok()?;
76
77        let layout = self
78            .reader
79            .inner()
80            .get_struct_layout(object_type)
81            .ok()
82            .flatten()?;
83
84        let root = sui_display::v2::OwnedSlice::new(layout, contents.to_owned());
85        let interpreter = sui_display::v2::Interpreter::new(root, DisplayStore::new(&self.reader));
86
87        let mut display = Display::default();
88
89        // The display api requires that the `sui_display::v2::Store` is async. We know that the
90        // Store implementation we are passing in here is fully synchronous, doing db access
91        // in-place and as such should never return Poll::Pending.
92        match display_template
93            .display::<prost_types::Value>(
94                self.config.display().max_move_value_depth(),
95                self.config.display().max_output_size(),
96                &interpreter,
97            )
98            .now_or_never()
99            .unwrap()
100        {
101            Ok(rendered) => {
102                let mut output = Struct::default();
103                let mut errors = Struct::default();
104
105                for (field, result) in rendered {
106                    match result {
107                        Ok(value) => {
108                            output.fields.insert(field, value);
109                        }
110                        Err(e) => {
111                            errors.fields.insert(field, e.to_string().into());
112                        }
113                    }
114                }
115
116                if !output.fields.is_empty() {
117                    display.set_output(output.fields);
118                }
119
120                if !errors.fields.is_empty() {
121                    display.set_errors(errors.fields);
122                }
123            }
124            Err(e) => {
125                display.set_errors(e.to_string());
126            }
127        }
128
129        Some(display)
130    }
131
132    pub fn render_events_to_proto(
133        &self,
134        events: &sui_types::effects::TransactionEvents,
135        mask: &FieldMaskTree,
136        output_objects: &ObjectSet,
137    ) -> TransactionEvents {
138        let mut message = TransactionEvents::default();
139
140        if mask.contains(TransactionEvents::BCS_FIELD) {
141            let mut bcs = Bcs::serialize(&events).unwrap();
142            bcs.name = Some("TransactionEvents".to_owned());
143            message.bcs = Some(bcs);
144        }
145
146        if mask.contains(TransactionEvents::DIGEST_FIELD) {
147            message.digest = Some(events.digest().to_string());
148        }
149
150        if let Some(event_mask) = mask.subtree(TransactionEvents::EVENTS_FIELD) {
151            message.events = events
152                .data
153                .iter()
154                .map(|event| self.render_event_to_proto(event, &event_mask, output_objects))
155                .collect();
156        }
157
158        message
159    }
160
161    pub fn render_event_to_proto(
162        &self,
163        event: &sui_types::event::Event,
164        mask: &FieldMaskTree,
165        output_objects: &ObjectSet,
166    ) -> Event {
167        let mut message = Event::default();
168
169        if mask.contains(Event::PACKAGE_ID_FIELD) {
170            message.set_package_id(event.package_id.to_canonical_string(true));
171        }
172
173        if mask.contains(Event::MODULE_FIELD) {
174            message.set_module(event.transaction_module.to_string());
175        }
176
177        if mask.contains(Event::SENDER_FIELD) {
178            message.sender = Some(event.sender.to_string());
179        }
180
181        if mask.contains(Event::EVENT_TYPE_FIELD) {
182            message.event_type = Some(event.type_.to_canonical_string(true));
183        }
184
185        if mask.contains(Event::CONTENTS_FIELD) {
186            let mut bcs = Bcs::from(event.contents.clone());
187            bcs.name = Some(event.type_.to_canonical_string(true));
188            message.contents = Some(bcs);
189        }
190
191        if mask.contains(Event::JSON_FIELD) {
192            message.json = self
193                .render_json(&event.type_, &event.contents, output_objects)
194                .map(Box::new);
195        }
196
197        message
198    }
199
200    // Renders clever error information in-place
201    pub fn render_clever_error(&self, effects: &mut TransactionEffects) {
202        use sui_rpc::proto::sui::rpc::v2::CleverError;
203        use sui_rpc::proto::sui::rpc::v2::MoveAbort;
204        use sui_rpc::proto::sui::rpc::v2::clever_error;
205        use sui_rpc::proto::sui::rpc::v2::execution_error::ErrorDetails;
206
207        let Some(move_abort) = effects
208            .status
209            .as_mut()
210            .and_then(|status| status.error.as_mut())
211            .and_then(|error| match &mut error.error_details {
212                Some(ErrorDetails::Abort(move_abort)) => Some(move_abort),
213                _ => None,
214            })
215        else {
216            return;
217        };
218
219        fn render(service: &RpcService, move_abort: &MoveAbort) -> Option<CleverError> {
220            let location = move_abort.location.as_ref()?;
221            let abort_code = move_abort.abort_code();
222            let package_id = location.package().parse::<sui_sdk_types::Address>().ok()?;
223            let module = location.module();
224
225            let package = {
226                let object = service.reader.inner().get_object(&package_id.into())?;
227                sui_package_resolver::Package::read_from_object(&object).ok()?
228            };
229
230            let clever_error = package.resolve_clever_error(module, abort_code)?;
231
232            let mut clever_error_message = CleverError::default();
233
234            match clever_error.error_info {
235                sui_package_resolver::ErrorConstants::None => {}
236                sui_package_resolver::ErrorConstants::Rendered {
237                    identifier,
238                    constant,
239                } => {
240                    clever_error_message.constant_name = Some(identifier);
241                    clever_error_message.value = Some(clever_error::Value::Rendered(constant));
242                }
243                sui_package_resolver::ErrorConstants::Raw { identifier, bytes } => {
244                    clever_error_message.constant_name = Some(identifier);
245                    clever_error_message.value = Some(clever_error::Value::Raw(bytes.into()));
246                }
247            }
248
249            clever_error_message.error_code = clever_error.error_code.map(Into::into);
250            clever_error_message.line_number = Some(clever_error.source_line_number.into());
251
252            Some(clever_error_message)
253        }
254
255        move_abort.clever_error = render(self, move_abort);
256    }
257
258    pub fn render_effects_to_proto(
259        &self,
260        effects: &sui_types::effects::TransactionEffects,
261        unchanged_loaded_runtime_objects: &[sui_types::storage::ObjectKey],
262        objects: &ObjectSet,
263        mask: &FieldMaskTree,
264    ) -> TransactionEffects {
265        // TODO consider inlining this function here to avoid needing to do the extra parsing below
266        let mut effects = TransactionEffects::merge_from(effects, mask);
267
268        if mask.contains(TransactionEffects::UNCHANGED_LOADED_RUNTIME_OBJECTS_FIELD) {
269            effects.unchanged_loaded_runtime_objects = unchanged_loaded_runtime_objects
270                .iter()
271                .map(Into::into)
272                .collect();
273        }
274
275        if mask.contains(TransactionEffects::CHANGED_OBJECTS_FIELD) {
276            for changed_object in effects.changed_objects.iter_mut() {
277                let Ok(object_id) = changed_object
278                    .object_id()
279                    .parse::<sui_types::base_types::ObjectID>()
280                else {
281                    continue;
282                };
283
284                if let Some(object) = objects.get(&sui_types::storage::ObjectKey(
285                    object_id,
286                    changed_object
287                        .input_version_opt()
288                        .unwrap_or_else(|| changed_object.output_version())
289                        .into(),
290                )) {
291                    changed_object.set_object_type(object_type_to_string(object.into()));
292                }
293            }
294        }
295
296        if mask.contains(TransactionEffects::UNCHANGED_CONSENSUS_OBJECTS_FIELD) {
297            for unchanged_consensus_object in effects.unchanged_consensus_objects.iter_mut() {
298                let Ok(object_id) = unchanged_consensus_object
299                    .object_id()
300                    .parse::<sui_types::base_types::ObjectID>()
301                else {
302                    continue;
303                };
304
305                if let Some(object) = objects.get(&sui_types::storage::ObjectKey(
306                    object_id,
307                    unchanged_consensus_object.version().into(),
308                )) {
309                    unchanged_consensus_object
310                        .set_object_type(object_type_to_string(object.into()));
311                }
312            }
313        }
314
315        // Try to render clever error info
316        self.render_clever_error(&mut effects);
317
318        effects
319    }
320}
321
322fn object_type_to_string(object_type: sui_types::base_types::ObjectType) -> String {
323    match object_type {
324        sui_types::base_types::ObjectType::Package => "package".to_owned(),
325        sui_types::base_types::ObjectType::Struct(move_object_type) => {
326            move_object_type.to_canonical_string(true)
327        }
328    }
329}