identity_iota_core/rebased/client/
read_only.rsuse std::future::Future;
use std::ops::Deref;
use std::pin::Pin;
use std::str::FromStr;
use crate::rebased::iota;
use crate::IotaDID;
use crate::IotaDocument;
use crate::NetworkName;
use anyhow::anyhow;
use anyhow::Context as _;
use futures::stream::FuturesUnordered;
use crate::iota_interaction_adapter::IotaClientAdapter;
use crate::rebased::migration::get_alias;
use crate::rebased::migration::get_identity;
use crate::rebased::migration::lookup;
use crate::rebased::migration::Identity;
use crate::rebased::Error;
use futures::StreamExt as _;
use identity_core::common::Url;
use identity_did::DID;
use identity_iota_interaction::move_types::language_storage::StructTag;
use identity_iota_interaction::rpc_types::EventFilter;
use identity_iota_interaction::rpc_types::IotaData as _;
use identity_iota_interaction::rpc_types::IotaObjectData;
use identity_iota_interaction::rpc_types::IotaObjectDataFilter;
use identity_iota_interaction::rpc_types::IotaObjectDataOptions;
use identity_iota_interaction::rpc_types::IotaObjectResponseQuery;
use identity_iota_interaction::rpc_types::OwnedObjectRef;
use identity_iota_interaction::types::base_types::IotaAddress;
use identity_iota_interaction::types::base_types::ObjectID;
use identity_iota_interaction::types::base_types::ObjectRef;
use identity_iota_interaction::IotaClientTrait;
use serde::de::DeserializeOwned;
use serde::Deserialize;
#[cfg(not(target_arch = "wasm32"))]
use identity_iota_interaction::IotaClient;
#[derive(Clone)]
pub struct IdentityClientReadOnly {
iota_client: IotaClientAdapter,
iota_identity_pkg_id: ObjectID,
migration_registry_id: ObjectID,
network: NetworkName,
}
impl Deref for IdentityClientReadOnly {
type Target = IotaClientAdapter;
fn deref(&self) -> &Self::Target {
&self.iota_client
}
}
impl IdentityClientReadOnly {
pub const fn package_id(&self) -> ObjectID {
self.iota_identity_pkg_id
}
pub const fn network(&self) -> &NetworkName {
&self.network
}
pub const fn migration_registry_id(&self) -> ObjectID {
self.migration_registry_id
}
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
} else {
pub async fn new(iota_client: IotaClient) -> Result<Self, Error> {
Self::new_internal(IotaClientAdapter::new(iota_client)?).await
}
}
}
async fn new_internal(iota_client: IotaClientAdapter) -> Result<Self, Error> {
let network = network_id(&iota_client).await?;
let metadata = iota::well_known_networks::network_metadata(&network).ok_or_else(|| {
Error::InvalidConfig(format!(
"unrecognized network \"{network}\". Use `new_with_pkg_id` instead."
))
})?;
let network = metadata.network_alias().unwrap_or(network);
let pkg_id = metadata.latest_pkg_id();
Ok(IdentityClientReadOnly {
iota_client,
iota_identity_pkg_id: pkg_id,
migration_registry_id: metadata.migration_registry(),
network,
})
}
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
} else {
pub async fn new_with_pkg_id(iota_client: IotaClient, iota_identity_pkg_id: ObjectID) -> Result<Self, Error> {
Self::new_with_pkg_id_internal(
IotaClientAdapter::new(iota_client)?,
iota_identity_pkg_id
).await
}
}
}
async fn new_with_pkg_id_internal(
iota_client: IotaClientAdapter,
iota_identity_pkg_id: ObjectID,
) -> Result<Self, Error> {
let IdentityPkgMetadata {
migration_registry_id, ..
} = identity_pkg_metadata(&iota_client, iota_identity_pkg_id).await?;
let network = network_id(&iota_client).await?;
Ok(Self {
iota_client,
iota_identity_pkg_id,
migration_registry_id,
network,
})
}
pub async fn get_object_by_id<T>(&self, id: ObjectID) -> Result<T, Error>
where
T: DeserializeOwned,
{
self
.read_api()
.get_object_with_options(id, IotaObjectDataOptions::new().with_content())
.await
.context("lookup request failed")
.and_then(|res| res.data.context("missing data in response"))
.and_then(|data| data.content.context("missing object content in data"))
.and_then(|content| content.try_into_move().context("not a move object"))
.and_then(|obj| {
serde_json::from_value(obj.fields.to_json_value())
.map_err(|err| anyhow!("failed to deserialize move object; {err}"))
})
.map_err(|e| Error::ObjectLookup(e.to_string()))
}
pub async fn get_object_ref_by_id(&self, obj: ObjectID) -> Result<Option<OwnedObjectRef>, Error> {
self
.read_api()
.get_object_with_options(obj, IotaObjectDataOptions::default().with_owner())
.await
.map(|response| {
response.data.map(|obj_data| OwnedObjectRef {
owner: obj_data.owner.expect("requested data"),
reference: obj_data.object_ref().into(),
})
})
.map_err(Error::from)
}
pub async fn find_owned_ref_for_address<P>(
&self,
address: IotaAddress,
tag: StructTag,
predicate: P,
) -> Result<Option<ObjectRef>, Error>
where
P: Fn(&IotaObjectData) -> bool,
{
let filter = IotaObjectResponseQuery::new_with_filter(IotaObjectDataFilter::StructType(tag));
let mut cursor = None;
loop {
let mut page = self
.read_api()
.get_owned_objects(address, Some(filter.clone()), cursor, None)
.await?;
let obj_ref = std::mem::take(&mut page.data)
.into_iter()
.filter_map(|res| res.data)
.find(|obj| predicate(obj))
.map(|obj_data| obj_data.object_ref());
cursor = page.next_cursor;
if obj_ref.is_some() {
return Ok(obj_ref);
}
if !page.has_next_page {
break;
}
}
Ok(None)
}
pub async fn resolve_did(&self, did: &IotaDID) -> Result<IotaDocument, Error> {
self
.get_identity(get_object_id_from_did(did)?)
.await?
.did_document(self.network())
}
pub async fn get_identity(&self, object_id: ObjectID) -> Result<Identity, Error> {
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
let all_futures = FuturesUnordered::<Pin<Box<dyn Future<Output = Result<Option<Identity>, Error>>>>>::new();
} else {
let all_futures = FuturesUnordered::<Pin<Box<dyn Future<Output = Result<Option<Identity>, Error>> + Send>>>::new();
}
}
all_futures.push(Box::pin(resolve_new(self, object_id)));
all_futures.push(Box::pin(resolve_migrated(self, object_id)));
all_futures.push(Box::pin(resolve_unmigrated(self, object_id)));
all_futures
.filter_map(|res| Box::pin(async move { res.ok().flatten() }))
.next()
.await
.ok_or_else(|| Error::DIDResolutionError(format!("could not find DID document for {object_id}")))
}
}
async fn network_id(iota_client: &IotaClientAdapter) -> Result<NetworkName, Error> {
let network_id = iota_client
.read_api()
.get_chain_identifier()
.await
.map_err(|e| Error::RpcError(e.to_string()))?;
Ok(network_id.try_into().expect("chain ID is a valid network name"))
}
#[derive(Debug)]
struct IdentityPkgMetadata {
migration_registry_id: ObjectID,
}
#[derive(Deserialize)]
struct MigrationRegistryCreatedEvent {
#[allow(dead_code)]
id: ObjectID,
}
async fn identity_pkg_metadata(
iota_client: &IotaClientAdapter,
package_id: ObjectID,
) -> Result<IdentityPkgMetadata, Error> {
let event_filter = EventFilter::MoveEventType(
StructTag::from_str(&format!("{package_id}::migration_registry::MigrationRegistryCreated")).expect("valid utf8"),
);
let mut returned_events = iota_client
.event_api()
.query_events(event_filter, None, Some(1), false)
.await
.map_err(|e| Error::RpcError(e.to_string()))?
.data;
let event = if !returned_events.is_empty() {
returned_events.swap_remove(0)
} else {
return Err(Error::InvalidConfig(
"no \"iota_identity\" package found on the provided network".to_string(),
));
};
let registry_id = serde_json::from_value::<MigrationRegistryCreatedEvent>(event.parsed_json)
.map(|e| e.id)
.map_err(|e| {
Error::MigrationRegistryNotFound(crate::rebased::migration::Error::NotFound(format!(
"Malformed \"MigrationRegistryEvent\": {}",
e
)))
})?;
Ok(IdentityPkgMetadata {
migration_registry_id: registry_id,
})
}
async fn resolve_new(client: &IdentityClientReadOnly, object_id: ObjectID) -> Result<Option<Identity>, Error> {
let onchain_identity = get_identity(client, object_id).await.map_err(|err| {
Error::DIDResolutionError(format!(
"could not get identity document for object id {object_id}; {err}"
))
})?;
Ok(onchain_identity.map(Identity::FullFledged))
}
async fn resolve_migrated(client: &IdentityClientReadOnly, object_id: ObjectID) -> Result<Option<Identity>, Error> {
let onchain_identity = lookup(client, object_id).await.map_err(|err| {
Error::DIDResolutionError(format!(
"failed to look up object_id {object_id} in migration registry; {err}"
))
})?;
let Some(mut onchain_identity) = onchain_identity else {
return Ok(None);
};
let object_id_str = object_id.to_string();
let queried_did = IotaDID::from_object_id(&object_id_str, &client.network);
let identity_did = onchain_identity.did_document().id().clone();
let doc = onchain_identity.did_document_mut();
*doc.core_document_mut().id_mut_unchecked() = queried_did.clone().into();
doc
.also_known_as_mut()
.replace::<Url>(&queried_did.into_url().into(), identity_did.into_url().into());
Ok(Some(Identity::FullFledged(onchain_identity)))
}
async fn resolve_unmigrated(client: &IdentityClientReadOnly, object_id: ObjectID) -> Result<Option<Identity>, Error> {
let unmigrated_alias = get_alias(client, object_id)
.await
.map_err(|err| Error::DIDResolutionError(format!("could no query for object id {object_id}; {err}")))?;
Ok(unmigrated_alias.map(Identity::Legacy))
}
pub fn get_object_id_from_did(did: &IotaDID) -> Result<ObjectID, Error> {
ObjectID::from_str(did.tag_str())
.map_err(|err| Error::DIDResolutionError(format!("could not parse object id from did {did}; {err}")))
}