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