Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sui ns rpc: resolve between address and name #10684

Merged
merged 3 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/sui-config/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ impl<R: rand::RngCore + rand::CryptoRng> ConfigBuilder<R> {
db_checkpoint_config: self.db_checkpoint_config.clone(),
indirect_objects_threshold: usize::MAX,
expensive_safety_check_config: Default::default(),
name_service_resolver_object_id: None,
}
})
.collect();
Expand Down
5 changes: 4 additions & 1 deletion crates/sui-config/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::usize;
use sui_keys::keypair_file::{read_authority_keypair_from_file, read_keypair_from_file};
use sui_protocol_config::SupportedProtocolVersions;
use sui_storage::object_store::ObjectStoreConfig;
use sui_types::base_types::SuiAddress;
use sui_types::base_types::{ObjectID, SuiAddress};
use sui_types::crypto::AuthorityPublicKeyBytes;
use sui_types::crypto::KeypairTraits;
use sui_types::crypto::NetworkKeyPair;
Expand Down Expand Up @@ -105,6 +105,9 @@ pub struct NodeConfig {

#[serde(default)]
pub expensive_safety_check_config: ExpensiveSafetyCheckConfig,

#[serde(skip_serializing_if = "Option::is_none")]
pub name_service_resolver_object_id: Option<ObjectID>,
}

fn default_authority_store_pruning_config() -> AuthorityStorePruningConfig {
Expand Down
1 change: 1 addition & 0 deletions crates/sui-config/src/swarm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ impl<'a> FullnodeConfigBuilder<'a> {
indirect_objects_threshold: usize::MAX,
// Copy the expensive safety check config from the first validator config.
expensive_safety_check_config: validator_config.expensive_safety_check_config.clone(),
name_service_resolver_object_id: None,
})
}
}
15 changes: 15 additions & 0 deletions crates/sui-indexer/src/apis/indexer_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,21 @@ where
spawn_subscription(sink, self.event_handler.subscribe(filter));
Ok(())
}

async fn resolve_name_service_address(&self, _name: String) -> RpcResult<SuiAddress> {
// TODO(gegaowp): implement name service resolver in indexer
todo!()
}

async fn resolve_name_service_names(
&self,
_address: SuiAddress,
_cursor: Option<ObjectID>,
_limit: Option<usize>,
) -> RpcResult<Page<String, ObjectID>> {
// TODO(gegaowp): implement name service resolver in indexer
todo!()
}
}

impl<S> SuiRpcModule for IndexerApi<S>
Expand Down
8 changes: 8 additions & 0 deletions crates/sui-json-rpc-types/src/sui_move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,14 @@ impl SuiMoveStruct {
}
}
}

pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<SuiMoveValue> {
match self {
SuiMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
SuiMoveStruct::WithTypes { type_: _, fields } => fields.get(field_name).cloned(),
_ => None,
}
}
}

impl Display for SuiMoveStruct {
Expand Down
44 changes: 44 additions & 0 deletions crates/sui-json-rpc-types/src/sui_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,32 @@ impl Display for SuiParsedData {
}
}

impl SuiParsedData {
pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
match object_read {
ObjectRead::NotExists(id) => Err(anyhow::anyhow!("Object {} does not exist", id)),
ObjectRead::Exists(_object_ref, o, layout) => {
let data = match o.data {
Data::Move(m) => {
let layout = layout.ok_or_else(|| {
anyhow!("Layout is required to convert Move object to json")
})?;
SuiParsedData::try_from_object(m, layout)?
}
Data::Package(p) => SuiParsedData::try_from_package(p)?,
};
Ok(data)
}
ObjectRead::Deleted((object_id, version, digest)) => Err(anyhow::anyhow!(
"Object {} was deleted at version {} with digest {}",
object_id,
version,
digest
)),
}
}
}

pub trait SuiMoveObject: Sized {
fn try_from_layout(object: MoveObject, layout: MoveStructLayout)
-> Result<Self, anyhow::Error>;
Expand Down Expand Up @@ -873,6 +899,24 @@ impl SuiMoveObject for SuiParsedMoveObject {
}
}

impl SuiParsedMoveObject {
pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
let parsed_data = SuiParsedData::try_from_object_read(object_read)?;
match parsed_data {
SuiParsedData::MoveObject(o) => Ok(o),
SuiParsedData::Package(_) => Err(anyhow::anyhow!("Object is not a Move object")),
}
}

pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<SuiMoveValue> {
match &self.fields {
SuiMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
SuiMoveStruct::WithTypes { fields, .. } => fields.get(field_name).cloned(),
_ => None,
}
}
}

pub fn type_and_fields_from_move_struct(
type_: &StructTag,
move_struct: MoveStruct,
Expand Down
21 changes: 20 additions & 1 deletion crates/sui-json-rpc/src/api/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use jsonrpsee::core::RpcResult;
use jsonrpsee_proc_macros::rpc;

use sui_json_rpc_types::{
DynamicFieldPage, EventFilter, EventPage, ObjectsPage, SuiEvent, SuiObjectResponse,
DynamicFieldPage, EventFilter, EventPage, ObjectsPage, Page, SuiEvent, SuiObjectResponse,
SuiObjectResponseQuery, SuiTransactionBlockResponseQuery, TransactionBlocksPage,
};
use sui_open_rpc_macros::open_rpc;
Expand Down Expand Up @@ -92,4 +92,23 @@ pub trait IndexerApi {
/// The Name of the dynamic field
name: DynamicFieldName,
) -> RpcResult<SuiObjectResponse>;

/// Return the resolved address given resolver and name
#[method(name = "resolveNameServiceAddress")]
async fn resolve_name_service_address(
&self,
/// The name to resolve
name: String,
) -> RpcResult<SuiAddress>;

/// Return the resolved names given address,
/// if multiple names are resolved, the first one is the primary name.
#[method(name = "resolveNameServiceNames")]
gegaowp marked this conversation as resolved.
Show resolved Hide resolved
async fn resolve_name_service_names(
&self,
/// The address to resolve
address: SuiAddress,
cursor: Option<ObjectID>,
limit: Option<usize>,
) -> RpcResult<Page<String, ObjectID>>;
}
183 changes: 178 additions & 5 deletions crates/sui-json-rpc/src/indexer_api.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::str::FromStr;
use std::sync::Arc;

use anyhow::anyhow;
Expand All @@ -13,12 +14,13 @@ use jsonrpsee::{RpcModule, SubscriptionSink};
use serde::Serialize;
use tracing::{debug, warn};

use move_core_types::language_storage::TypeTag;
use mysten_metrics::spawn_monitored_task;
use sui_core::authority::AuthorityState;
use sui_json_rpc_types::{
DynamicFieldPage, EventFilter, EventPage, ObjectsPage, Page, SuiObjectDataOptions,
SuiObjectResponse, SuiObjectResponseQuery, SuiTransactionBlockResponse,
SuiTransactionBlockResponseQuery, TransactionBlocksPage,
DynamicFieldPage, EventFilter, EventPage, ObjectsPage, Page, SuiMoveValue,
SuiObjectDataOptions, SuiObjectResponse, SuiObjectResponseQuery, SuiParsedMoveObject,
SuiTransactionBlockResponse, SuiTransactionBlockResponseQuery, TransactionBlocksPage,
};
use sui_open_rpc::Module;
use sui_types::base_types::{ObjectID, SuiAddress};
Expand All @@ -31,6 +33,13 @@ use crate::api::{
};
use crate::SuiRpcModule;

const NAME_SERVICE_LOOKUP_KEY: &str = "records";
const NAME_SERVICE_REVERSE_LOOKUP_KEY: &str = "reverse";
const NAME_SERVICE_VALUE: &str = "value";
const NAME_SERVICE_ID: &str = "id";
const NAME_SERVICE_MARKER: &str = "marker";
const STRING_TYPE_TAG: &str = "0x1::string::String";

pub fn spawn_subscription<S, T>(mut sink: SubscriptionSink, rx: S)
where
S: Stream<Item = T> + Unpin + Send + 'static,
Expand All @@ -53,11 +62,16 @@ where
pub struct IndexerApi<R> {
state: Arc<AuthorityState>,
read_api: R,
ns_resolver_id: Option<ObjectID>,
}

impl<R: ReadApiServer> IndexerApi<R> {
pub fn new(state: Arc<AuthorityState>, read_api: R) -> Self {
Self { state, read_api }
pub fn new(state: Arc<AuthorityState>, read_api: R, ns_resolver_id: Option<ObjectID>) -> Self {
Self {
state,
read_api,
ns_resolver_id,
}
}
}

Expand Down Expand Up @@ -216,6 +230,112 @@ impl<R: ReadApiServer> IndexerApiServer for IndexerApi<R> {
self.read_api
.get_object(id, Some(SuiObjectDataOptions::full_content()))
}

async fn resolve_name_service_address(&self, name: String) -> RpcResult<SuiAddress> {
let dynmaic_field_table_object_id =
self.get_name_service_dynamic_field_table_object_id(/* reverse_lookup */ false)?;
// NOTE: 0x1::string::String is the type tag of fields in dynmaic_field_table
let ns_type_tag = TypeTag::from_str(STRING_TYPE_TAG)?;
let ns_dynamic_field_name = DynamicFieldName {
type_: ns_type_tag,
value: name.clone().into(),
};
// record of the input `name`
let record_object_id = self
.state
.get_dynamic_field_object_id(dynmaic_field_table_object_id, &ns_dynamic_field_name)
.map_err(|e| {
anyhow!(
"Read name service dynamic field table failed with error: {:?}",
e
)
})?
.ok_or_else(|| anyhow!("Record not found for name: {:?}", name,))?;
let record_object_read = self.state.get_object_read(&record_object_id).map_err(|e| {
warn!(
"Failed to get object read of name: {:?} with error: {:?}",
record_object_id, e
);
anyhow!("{e}")
})?;
let record_parsed_move_object =
SuiParsedMoveObject::try_from_object_read(record_object_read)?;
// NOTE: "value" is the field name to get the address info
let address_info_move_value = record_parsed_move_object
.read_dynamic_field_value(NAME_SERVICE_VALUE)
.ok_or_else(|| anyhow!("Cannot find value field in record Move struct"))?;
let address_info_move_struct = match address_info_move_value {
SuiMoveValue::Struct(a) => Ok(a),
_ => Err(anyhow!("value field is not found.")),
}?;
// NOTE: "marker" is the field name to get the address
let address_str_move_value = address_info_move_struct
.read_dynamic_field_value(NAME_SERVICE_MARKER)
.ok_or_else(|| {
anyhow!(
"Cannot find marker field in address info Move struct: {:?}",
address_info_move_struct
)
})?;
let addr = match address_str_move_value {
SuiMoveValue::Address(addr) => Ok(addr),
_ => Err(anyhow!(
"No SuiAddress found in: {:?}",
address_str_move_value
)),
}?;
Ok(addr)
}

async fn resolve_name_service_names(
&self,
address: SuiAddress,
_cursor: Option<ObjectID>,
_limit: Option<usize>,
) -> RpcResult<Page<String, ObjectID>> {
let dynmaic_field_table_object_id =
self.get_name_service_dynamic_field_table_object_id(/* reverse_lookup */ true)?;
let addr_type_tag = TypeTag::Address;
let ns_dynamic_field_name = DynamicFieldName {
type_: addr_type_tag,
value: address.to_string().into(),
};

let addr_object_id = self
.state
.get_dynamic_field_object_id(dynmaic_field_table_object_id, &ns_dynamic_field_name)
.map_err(|e| {
anyhow!(
"Read name service reverse dynamic field table failed with error: {:?}",
e
)
})?
.ok_or_else(|| anyhow!("Record not found for address: {:?}", address))?;
let addr_object_read = self.state.get_object_read(&addr_object_id).map_err(|e| {
warn!(
"Failed to get object read of address {:?} with error: {:?}",
addr_object_id, e
);
anyhow!("{e}")
})?;
let addr_parsed_move_object = SuiParsedMoveObject::try_from_object_read(addr_object_read)?;
let address_info_move_value = addr_parsed_move_object
.read_dynamic_field_value(NAME_SERVICE_VALUE)
.ok_or_else(|| anyhow!("Cannot find value field in record Move struct"))?;

let primary_name = match address_info_move_value {
SuiMoveValue::String(s) => Ok(s),
_ => Err(anyhow!(
"No string field for primary name is found in {:?}",
address_info_move_value
)),
}?;
Ok(Page {
data: vec![primary_name],
next_cursor: Some(addr_object_id),
has_next_page: false,
})
}
}

impl<R: ReadApiServer> SuiRpcModule for IndexerApi<R> {
Expand All @@ -227,3 +347,56 @@ impl<R: ReadApiServer> SuiRpcModule for IndexerApi<R> {
crate::api::IndexerApiOpenRpc::module_doc()
}
}

impl<R: ReadApiServer> IndexerApi<R> {
fn get_name_service_dynamic_field_table_object_id(
&self,
reverse_lookup: bool,
) -> RpcResult<ObjectID> {
if let Some(resolver_id) = self.ns_resolver_id {
let resolver_object_read = self.state.get_object_read(&resolver_id).map_err(|e| {
warn!(
"Failed to get object read of resolver {:?} with error: {:?}",
resolver_id, e
);
anyhow!("{e}")
})?;

let resolved_parsed_move_object =
SuiParsedMoveObject::try_from_object_read(resolver_object_read)?;
// NOTE: "records" is the field name to get forward lookup table,
// "reverse" is the field name to get reverse lookup table.
let dynamic_field_table_key = if reverse_lookup {
NAME_SERVICE_REVERSE_LOOKUP_KEY
} else {
NAME_SERVICE_LOOKUP_KEY
};
let records_value = resolved_parsed_move_object
.read_dynamic_field_value(dynamic_field_table_key)
.ok_or_else(|| anyhow!("Cannot find records field in resolved object"))?;
let records_move_struct = match records_value {
SuiMoveValue::Struct(s) => Ok(s),
_ => Err(anyhow!(
"{} field is not a Move struct",
dynamic_field_table_key
)),
}?;

let dynmaic_field_table_object_id_struct = records_move_struct
.read_dynamic_field_value(NAME_SERVICE_ID)
.ok_or_else(|| {
anyhow!(
"Cannot find id field in {} Move struct",
dynamic_field_table_key
)
})?;
let dynmaic_field_table_object_id = match dynmaic_field_table_object_id_struct {
SuiMoveValue::UID { id } => Ok(id),
_ => Err(anyhow!("id field is not a UID")),
}?;
Ok(dynmaic_field_table_object_id)
} else {
Err(anyhow!("Name service resolver is not set"))?
}
}
}
Loading