sui_indexer_alt_jsonrpc/api/
dynamic_fields.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use anyhow::{anyhow, Context as _};
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use sui_json::SuiJsonValue;
use sui_json_rpc_types::{SuiObjectDataOptions, SuiObjectResponse};
use sui_open_rpc::Module;
use sui_open_rpc_macros::open_rpc;
use sui_types::{
    base_types::ObjectID,
    dynamic_field::{derive_dynamic_field_id, DynamicFieldInfo, DynamicFieldName},
    error::SuiObjectResponseError,
    object::Object,
    TypeTag,
};
use tokio::try_join;

use crate::{
    context::Context,
    data::objects::load_live,
    error::{invalid_params, rpc_bail, RpcError},
};

use super::{objects, rpc_module::RpcModule};

#[open_rpc(namespace = "suix", tag = "Dynamic Fields API")]
#[rpc(server, namespace = "suix")]
trait DynamicFieldsApi {
    /// Return the information from a dynamic field based on its parent ID and name.
    #[method(name = "getDynamicFieldObject")]
    async fn get_dynamic_field_object(
        &self,
        /// The ID of the parent object
        parent_object_id: ObjectID,
        /// The Name of the dynamic field
        name: DynamicFieldName,
    ) -> RpcResult<SuiObjectResponse>;
}

pub struct DynamicFields(pub Context);

#[derive(thiserror::Error, Debug)]
enum Error {
    #[error("Bad dynamic field name: {0}")]
    BadName(anyhow::Error),

    #[error("Invalid type {0}: {1}")]
    BadType(TypeTag, sui_package_resolver::error::Error),

    #[error("Could not serialize dynamic field name as {0}: {1}")]
    TypeMismatch(TypeTag, anyhow::Error),
}

#[async_trait::async_trait]
impl DynamicFieldsApiServer for DynamicFields {
    async fn get_dynamic_field_object(
        &self,
        parent_object_id: ObjectID,
        name: DynamicFieldName,
    ) -> RpcResult<SuiObjectResponse> {
        let Self(ctx) = self;
        Ok(dynamic_field_object_response(ctx, parent_object_id, name).await?)
    }
}

impl RpcModule for DynamicFields {
    fn schema(&self) -> Module {
        DynamicFieldsApiOpenRpc::module_doc()
    }

    fn into_impl(self) -> jsonrpsee::RpcModule<Self> {
        self.into_rpc()
    }
}

async fn dynamic_field_object_response(
    ctx: &Context,
    parent_object_id: ObjectID,
    name: DynamicFieldName,
) -> Result<SuiObjectResponse, RpcError<Error>> {
    let layout = ctx
        .package_resolver()
        .type_layout(name.type_.clone())
        .await
        .map_err(|e| {
            use sui_package_resolver::error::Error as PRE;
            match &e {
                // These errors can be triggered by passing a type that doesn't exist for the
                // dynamic field name.
                PRE::NotAPackage(_)
                | PRE::PackageNotFound(_)
                | PRE::ModuleNotFound(_, _)
                | PRE::DatatypeNotFound(_, _, _)
                | PRE::TypeArityMismatch(_, _) => {
                    invalid_params(Error::BadType(name.type_.clone(), e))
                }

                // These errors can be triggered by requesting a type whose layout is too large
                // (requires too may resources to resolve)
                PRE::TooManyTypeNodes(_, _)
                | PRE::TooManyTypeParams(_, _)
                | PRE::TypeParamNesting(_, _) => {
                    invalid_params(Error::BadType(name.type_.clone(), e))
                }

                // The other errors are a form of internal error.
                PRE::Bcs(_)
                | PRE::Store { .. }
                | PRE::Deserialize(_)
                | PRE::EmptyPackage(_)
                | PRE::FunctionNotFound(_, _, _)
                | PRE::LinkageNotFound(_)
                | PRE::NoTypeOrigin(_, _, _)
                | PRE::NotAnIdentifier(_)
                | PRE::TypeParamOOB(_, _)
                | PRE::UnexpectedReference
                | PRE::UnexpectedSigner
                | PRE::UnexpectedError(_)
                | PRE::ValueNesting(_) => {
                    RpcError::from(anyhow!(e).context("Failed to resolve type layout"))
                }
            }
        })?;

    let bytes = SuiJsonValue::new(name.value)
        .map_err(|e| invalid_params(Error::BadName(e)))?
        .to_bcs_bytes(&layout)
        .map_err(|e| invalid_params(Error::TypeMismatch(name.type_.clone(), e)))?;

    let df = load_df(ctx, parent_object_id, &name.type_, &bytes);
    let dof = load_dof(ctx, parent_object_id, &name.type_, &bytes);
    let (df, dof) = try_join!(df, dof)
        .with_context(|| format!("Failed to fetch dynamic field on {parent_object_id}"))?;

    let Some(object) = df.or(dof) else {
        return Ok(SuiObjectResponse::new_with_error(
            SuiObjectResponseError::DynamicFieldNotFound { parent_object_id },
        ));
    };

    let options = SuiObjectDataOptions::full_content();

    use RpcError as E;
    Ok(SuiObjectResponse::new_with_data(
        objects::response::object_data_with_options(ctx, object, &options)
            .await
            .map_err(|e| match e {
                E::InvalidParams(e) => match e {},
                E::InternalError(e) => E::InternalError(e),
            })?,
    ))
}

/// Try to load a dynamic field from `parent_id`, whose name has type `type_` and value `name` (as
/// BCS bytes). Fetches the `Field<K, V>` object from store.
async fn load_df(
    ctx: &Context,
    parent_id: ObjectID,
    type_: &TypeTag,
    value: &[u8],
) -> Result<Option<Object>, RpcError<Error>> {
    let id = derive_dynamic_field_id(parent_id, type_, value)
        .context("Failed to derive dynamic field ID")?;

    Ok(load_live(ctx, id)
        .await
        .context("Failed to load dynamic field")?)
}

/// Try to load a dynamic object field from `parent_id`, whose name has type `type_` and value
/// `name` (as BCS bytes). Fetches the object pointed to by the `Field<Wrapper<K>, ID>` object.
///
/// This function returns `None`, if the Field object does not exist in the store or does not have
/// contents.
async fn load_dof(
    ctx: &Context,
    parent_id: ObjectID,
    type_: &TypeTag,
    name: &[u8],
) -> Result<Option<Object>, RpcError<Error>> {
    let wrapper: TypeTag = DynamicFieldInfo::dynamic_object_field_wrapper(type_.clone()).into();
    let id = derive_dynamic_field_id(parent_id, &wrapper, name)
        .context("Failed to derive dynamic object field ID")?;

    let Some(object) = load_live(ctx, id)
        .await
        .context("Failed to load dynamic object field")?
    else {
        return Ok(None);
    };

    let Some(move_object) = object.data.try_as_move() else {
        rpc_bail!("Dynamic field at {id} is not a Move Object");
    };

    // Peel off the UID and the name from the serialized object. An ObjectID should be left.
    let value = ObjectID::from_bytes(&move_object.contents()[ObjectID::LENGTH + name.len()..])
        .context("Failed to extract object ID from dynamic object field")?;

    Ok(load_live(ctx, value)
        .await
        .context("Failed to load dynamic field object")?)
}