identity_iota_core/rebased/migration/
registry.rs

1// Copyright 2020-2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::HashMap;
5use std::str::FromStr as _;
6use std::sync::LazyLock;
7
8use iota_interaction::move_types::language_storage::StructTag;
9use iota_interaction::rpc_types::EventFilter;
10use iota_interaction::rpc_types::IotaData;
11use iota_interaction::types::base_types::ObjectID;
12use iota_interaction::types::id::ID;
13use iota_interaction::IotaClientTrait;
14use phf::phf_map;
15use phf::Map;
16use product_common::core_client::CoreClientReadOnly;
17use tokio::sync::RwLock;
18
19use crate::rebased::client::IdentityClientReadOnly;
20use crate::rebased::iota::package::identity_package_id;
21
22use super::get_identity;
23use super::OnChainIdentity;
24
25static MIGRATION_REGISTRY_ON_IOTA_NETWORK: Map<&str, &str> = phf_map! {
26  "e678123a" => "0x940ae1c2c48dade9ec01cc1eebab33ab6fecadda422ea18b105c47839fc64425", // Devnet
27  "2304aa97" => "0xaacb529c289aec9de2a474faaa4ef68b04632bb6a5d08372ca5b60e3df659f59", // Testnet
28  "6364aad5" => "0xa884c72da9971da8ec32efade0a9b05faa114770ba85f10925d0edbc3fa3edc3", // Mainnet
29};
30
31static MIGRATION_REGISTRY_ON_CUSTOM_NETWORK: LazyLock<RwLock<HashMap<String, ObjectID>>> =
32  LazyLock::new(|| RwLock::new(HashMap::default()));
33
34pub(crate) async fn migration_registry_id<C>(client: &C) -> Result<ObjectID, Error>
35where
36  C: CoreClientReadOnly,
37{
38  let network_id = client.network_name().as_ref();
39
40  // The registry has already been computed for this network.
41  if let Some(registry) = MIGRATION_REGISTRY_ON_CUSTOM_NETWORK.read().await.get(network_id) {
42    return Ok(*registry);
43  }
44
45  // Client is connected to a well-known network.
46  if let Some(registry) = MIGRATION_REGISTRY_ON_IOTA_NETWORK.get(network_id) {
47    return Ok(registry.parse().unwrap());
48  }
49
50  let package_id = identity_package_id(client)
51    .await
52    .map_err(|_| Error::Client(anyhow::anyhow!("unknown network {network_id}")))?;
53  let registry_id = find_migration_registry(client, package_id).await?;
54
55  // Cache registry for network.
56  MIGRATION_REGISTRY_ON_CUSTOM_NETWORK
57    .write()
58    .await
59    .insert(network_id.to_string(), registry_id);
60
61  Ok(package_id)
62}
63
64pub(crate) fn set_migration_registry_id(chain_id: &str, id: ObjectID) {
65  MIGRATION_REGISTRY_ON_CUSTOM_NETWORK
66    .blocking_write()
67    .insert(chain_id.to_owned(), id);
68}
69
70/// Errors that can occur during migration registry operations.
71#[derive(thiserror::Error, Debug)]
72pub enum Error {
73  /// An error occurred while interacting with the IOTA Client.
74  #[error(transparent)]
75  Client(#[from] anyhow::Error),
76  /// The MigrationRegistry object was not found.
77  #[error("could not locate MigrationRegistry object: {0}")]
78  NotFound(String),
79  /// The MigrationRegistry object is malformed.
80  #[error("malformed MigrationRegistry's entry: {0}")]
81  Malformed(String),
82}
83
84/// Lookup a legacy `alias_id` into the migration registry
85/// to get the ID of the corresponding migrated DID document, if any.
86pub async fn lookup(id_client: &IdentityClientReadOnly, alias_id: ObjectID) -> Result<Option<OnChainIdentity>, Error> {
87  let registry_id = migration_registry_id(id_client).await?;
88  let dynamic_field_name = serde_json::from_value(serde_json::json!({
89    "type": "0x2::object::ID",
90    "value": alias_id.to_string()
91  }))
92  .expect("valid move value");
93
94  let identity_id = id_client
95    .read_api()
96    .get_dynamic_field_object(registry_id, dynamic_field_name)
97    .await
98    .map_err(|e| Error::Client(e.into()))?
99    .data
100    .map(|data| {
101      data
102        .content
103        .and_then(|content| content.try_into_move())
104        .and_then(|move_object| move_object.fields.to_json_value().get_mut("value").map(std::mem::take))
105        .and_then(|value| serde_json::from_value::<ID>(value).map(|id| id.bytes).ok())
106        .ok_or(Error::Malformed(
107          "invalid MigrationRegistry's Entry encoding".to_string(),
108        ))
109    })
110    .transpose()?;
111
112  if let Some(id) = identity_id {
113    get_identity(id_client, id).await.map_err(|e| Error::Client(e.into()))
114  } else {
115    Ok(None)
116  }
117}
118
119async fn find_migration_registry<C>(iota_client: &C, package_id: ObjectID) -> Result<ObjectID, Error>
120where
121  C: CoreClientReadOnly,
122{
123  #[derive(serde::Deserialize)]
124  struct MigrationRegistryCreatedEvent {
125    id: ObjectID,
126  }
127
128  let event_filter = EventFilter::MoveEventType(
129    StructTag::from_str(&format!("{package_id}::migration_registry::MigrationRegistryCreated")).expect("valid utf8"),
130  );
131  let mut returned_events = iota_client
132    .client_adapter()
133    .event_api()
134    .query_events(event_filter, None, Some(1), false)
135    .await
136    .map_err(|e| Error::Client(e.into()))?
137    .data;
138  let event = if !returned_events.is_empty() {
139    returned_events.swap_remove(0)
140  } else {
141    return Err(Error::NotFound(format!(
142      "No MigrationRegistryCreated event on network {}",
143      iota_client.network_name()
144    )));
145  };
146
147  serde_json::from_value::<MigrationRegistryCreatedEvent>(event.parsed_json)
148    .map(|e| e.id)
149    .map_err(|e| Error::Malformed(e.to_string()))
150}