identity_iota_core/rebased/migration/
alias.rs1use async_trait::async_trait;
5use identity_core::common::Url;
6use identity_did::DID as _;
7use iota_interaction::rpc_types::IotaExecutionStatus;
8use iota_interaction::rpc_types::IotaObjectDataOptions;
9use iota_interaction::rpc_types::IotaTransactionBlockEffects;
10use iota_interaction::rpc_types::IotaTransactionBlockEffectsAPI as _;
11use iota_interaction::types::base_types::IotaAddress;
12use iota_interaction::types::base_types::ObjectID;
13use iota_interaction::types::id::UID;
14use iota_interaction::types::transaction::ProgrammableTransaction;
15use iota_interaction::types::TypeTag;
16use iota_interaction::types::STARDUST_PACKAGE_ID;
17use iota_interaction::IotaTransactionBlockEffectsMutAPI as _;
18use iota_interaction::OptionalSync;
19
20use product_common::core_client::CoreClientReadOnly;
21use product_common::transaction::transaction_builder::Transaction;
22use serde;
23use serde::Deserialize;
24use serde::Serialize;
25use tokio::sync::OnceCell;
26
27use crate::rebased::client::IdentityClientReadOnly;
28
29use crate::rebased::iota::move_calls;
30use crate::rebased::iota::package::identity_package_id;
31use crate::rebased::Error;
32use crate::IotaDID;
33use iota_interaction::IotaClientTrait;
34use iota_interaction::MoveType;
35
36use super::get_identity;
37use super::migration_registry_id;
38use super::Identity;
39use super::OnChainIdentity;
40
41#[derive(Clone, Debug, Deserialize, Serialize)]
43pub struct UnmigratedAlias {
44 pub id: UID,
47
48 pub legacy_state_controller: Option<IotaAddress>,
50 pub state_index: u32,
52 pub state_metadata: Option<Vec<u8>>,
54
55 pub sender: Option<IotaAddress>,
57
58 pub immutable_issuer: Option<IotaAddress>,
60 pub immutable_metadata: Option<Vec<u8>>,
62}
63
64impl MoveType for UnmigratedAlias {
65 fn move_type(_: ObjectID) -> TypeTag {
66 format!("{STARDUST_PACKAGE_ID}::alias::Alias")
67 .parse()
68 .expect("valid move type")
69 }
70}
71
72pub async fn get_alias(client: &IdentityClientReadOnly, object_id: ObjectID) -> Result<Option<UnmigratedAlias>, Error> {
74 match client.get_object_by_id(object_id).await {
75 Ok(Some(alias)) => Ok(Some(alias)),
76 Ok(None) => Ok(None),
77 Err(e) => Err(e.into()),
78 }
79}
80
81pub struct MigrateLegacyIdentity {
84 alias: UnmigratedAlias,
85 cached_ptb: OnceCell<ProgrammableTransaction>,
86}
87
88impl MigrateLegacyIdentity {
89 pub fn new(alias: UnmigratedAlias) -> Self {
91 Self {
92 alias,
93 cached_ptb: OnceCell::new(),
94 }
95 }
96
97 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
98 where
99 C: CoreClientReadOnly + OptionalSync,
100 {
101 let identity = Identity::Legacy(self.alias.clone());
103 let did_doc = identity.did_document(client.network_name())?;
104 let Identity::Legacy(alias) = identity else {
105 unreachable!("alias was wrapped by us")
106 };
107 let dynamic_field_wrapper = client
109 .client_adapter()
110 .read_api()
111 .get_object_with_options(*alias.id.object_id(), IotaObjectDataOptions::new().with_owner())
112 .await
113 .map_err(|e| Error::RpcError(e.to_string()))?
114 .owner()
115 .expect("owner was requested")
116 .get_owner_address()
117 .expect("alias is a dynamic field")
118 .into();
119 let alias_output_id = client
120 .client_adapter()
121 .read_api()
122 .get_object_with_options(dynamic_field_wrapper, IotaObjectDataOptions::new().with_owner())
123 .await
124 .map_err(|e| Error::RpcError(e.to_string()))?
125 .owner()
126 .expect("owner was requested")
127 .get_owner_address()
128 .expect("alias is owned by an alias_output")
129 .into();
130 let alias_output_ref = client
132 .client_adapter()
133 .read_api()
134 .get_object_with_options(alias_output_id, IotaObjectDataOptions::default())
135 .await
136 .map_err(|e| Error::RpcError(e.to_string()))?
137 .object_ref_if_exists()
138 .expect("alias_output exists");
139 let migration_registry_id = migration_registry_id(client)
141 .await
142 .map_err(Error::MigrationRegistryNotFound)?;
143 let migration_registry_ref = client
144 .get_object_ref_by_id(migration_registry_id)
145 .await?
146 .expect("migration registry exists");
147
148 let created = did_doc
150 .metadata
151 .created
152 .map(|timestamp| timestamp.to_unix() as u64 * 1000);
154
155 let package = identity_package_id(client).await?;
156
157 let tx = move_calls::migration::migrate_did_output(alias_output_ref, created, migration_registry_ref, package)
159 .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
160
161 Ok(bcs::from_bytes(&tx)?)
162 }
163}
164
165#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
166#[cfg_attr(feature = "send-sync", async_trait)]
167impl Transaction for MigrateLegacyIdentity {
168 type Output = OnChainIdentity;
169 type Error = Error;
170
171 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
172 where
173 C: CoreClientReadOnly + OptionalSync,
174 {
175 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
176 }
177
178 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, client: &C) -> Result<Self::Output, Self::Error>
179 where
180 C: CoreClientReadOnly + OptionalSync,
181 {
182 if let IotaExecutionStatus::Failure { error } = effects.status() {
183 return Err(Error::TransactionUnexpectedResponse(error.to_string()));
184 }
185
186 let legacy_did: Url = IotaDID::new(&self.alias.id.object_id().into_bytes(), client.network_name())
187 .to_url()
188 .into();
189 let is_target_identity =
190 |identity: &OnChainIdentity| -> bool { identity.did_document().also_known_as().contains(&legacy_did) };
191
192 let created_objects = effects
193 .created()
194 .iter()
195 .enumerate()
196 .filter(|(_, obj_ref)| obj_ref.owner.is_shared())
197 .map(|(i, obj_ref)| (i, obj_ref.object_id()));
198
199 let mut target_identity_pos = None;
200 let mut target_identity = None;
201 for (i, obj_id) in created_objects {
202 match get_identity(client, obj_id).await {
203 Ok(Some(identity)) if is_target_identity(&identity) => {
204 target_identity_pos = Some(i);
205 target_identity = Some(identity);
206 break;
207 }
208 _ => continue,
209 }
210 }
211
212 let (Some(i), Some(identity)) = (target_identity_pos, target_identity) else {
213 return Err(Error::TransactionUnexpectedResponse(
214 "failed to find the correct identity in this transaction's effects".to_owned(),
215 ));
216 };
217
218 effects.created_mut().swap_remove(i);
219
220 Ok(identity)
221 }
222}