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

Support multiple IOTA networks in the Resolver #1304

Merged
merged 13 commits into from
Feb 21, 2024
4 changes: 3 additions & 1 deletion identity_iota_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ rustdoc-args = ["--cfg", "docsrs"]
default = ["client", "iota-client", "revocation-bitmap", "send-sync-client-ext"]
# Exposes the IotaIdentityClient and IotaIdentityClientExt traits.
client = ["dep:async-trait", "iota-sdk"]
# Enbales the implementation of the extension traits on the iota-sdk's Client.
# Enables the implementation of the extension traits on the iota-sdk's Client.
iota-client = ["client", "iota-sdk/client", "iota-sdk/tls"]
# Enables revocation with `RevocationBitmap2022`.
revocation-bitmap = ["identity_credential/revocation-bitmap"]
# Adds Send bounds on the futures produces by the client extension traits.
send-sync-client-ext = []
# Disables the blanket implementation of `IotaIdentityClientExt`.
test = ["client"]
7 changes: 6 additions & 1 deletion identity_iota_core/src/client/identity_client.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use crate::block::protocol::ProtocolParameters;
#[cfg(feature = "test")]
use iota_sdk::client::Client;

use crate::block::address::Address;
use crate::block::output::feature::SenderFeature;
Expand All @@ -14,6 +15,7 @@ use crate::block::output::Feature;
use crate::block::output::OutputId;
use crate::block::output::RentStructure;
use crate::block::output::UnlockCondition;
use crate::block::protocol::ProtocolParameters;
use crate::Error;
use crate::IotaDID;
use crate::IotaDocument;
Expand Down Expand Up @@ -192,7 +194,10 @@ pub trait IotaIdentityClientExt: IotaIdentityClient {
}
}

#[cfg(not(feature = "test"))]
impl<T> IotaIdentityClientExt for T where T: IotaIdentityClient {}
#[cfg(feature = "test")]
impl IotaIdentityClientExt for Client {}

pub(super) async fn validate_network<T>(client: &T, did: &IotaDID) -> Result<()>
where
Expand Down
2 changes: 2 additions & 0 deletions identity_resolver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ features = ["send-sync-client-ext", "iota-client"]
optional = true

[dev-dependencies]
identity_iota_core = { version = "=1.1.1", path = "../identity_iota_core", features = ["test"] }
iota-sdk = { version = "1.0.2" }
tokio = { version = "1.29.0", default-features = false, features = ["rt-multi-thread", "macros"] }

[features]
Expand Down
3 changes: 3 additions & 0 deletions identity_resolver/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,7 @@ pub enum ErrorCause {
/// The method that is unsupported.
method: String,
},
/// No client attached to the specific network.
#[error("none of the attached clients support the network {0}")]
UnsupportedNetwork(String),
}
118 changes: 116 additions & 2 deletions identity_resolver/src/resolution/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,12 +249,14 @@ impl<DOC: 'static> Resolver<DOC, SingleThreadedCommand<DOC>> {

#[cfg(feature = "iota")]
mod iota_handler {
use crate::ErrorCause;

use super::Resolver;
use identity_document::document::CoreDocument;
use identity_iota_core::IotaClientExt;
use identity_iota_core::IotaDID;
use identity_iota_core::IotaDocument;
use identity_iota_core::IotaIdentityClientExt;
use std::collections::HashMap;
use std::sync::Arc;

impl<DOC> Resolver<DOC>
Expand All @@ -266,7 +268,7 @@ mod iota_handler {
/// See also [`attach_handler`](Self::attach_handler).
pub fn attach_iota_handler<CLI>(&mut self, client: CLI)
where
CLI: IotaClientExt + Send + Sync + 'static,
CLI: IotaIdentityClientExt + Send + Sync + 'static,
{
let arc_client: Arc<CLI> = Arc::new(client);

Expand All @@ -277,6 +279,58 @@ mod iota_handler {

self.attach_handler(IotaDID::METHOD.to_owned(), handler);
}

/// Convenience method for attaching multiple handlers responsible for resolving IOTA DIDs
/// on multiple networks.
///
///
/// # Arguments
///
/// * `clients` - A collection of tuples where each tuple contains the name of the network name and its
/// corresponding client.
///
/// # Examples
///
/// ```ignore
/// // Assume `smr_client` and `iota_client` are instances IOTA clients `iota_sdk::client::Client`.
/// attach_multiple_iota_handlers(vec![("smr", smr_client), ("iota", iota_client)]);
/// ```
///
/// # See Also
/// - [`attach_handler`](Self::attach_handler).
///
/// # Note
///
/// - Using `attach_iota_handler` or `attach_handler` for the IOTA method would override all
abdulmth marked this conversation as resolved.
Show resolved Hide resolved
/// clients added in this method.
abdulmth marked this conversation as resolved.
Show resolved Hide resolved
/// - This function does not validate the provided configuration. Ensure that the provided
/// network name corresponds with the client, possibly by using `client.network_name()`.
pub fn attach_multiple_iota_handlers<CLI, I>(&mut self, clients: I)
where
CLI: IotaIdentityClientExt + Send + Sync + 'static,
I: IntoIterator<Item = (&'static str, CLI)>,
{
let arc_clients = Arc::new(clients.into_iter().collect::<HashMap<&'static str, CLI>>());

let handler = move |did: IotaDID| {
let future_client = arc_clients.clone();
async move {
let did_network = did.network_str();
let client: &CLI =
future_client
.get(did_network)
.ok_or(crate::Error::new(ErrorCause::UnsupportedNetwork(
did_network.to_string(),
)))?;
client
.resolve_did(&did)
.await
.map_err(|err| crate::Error::new(ErrorCause::HandlerError { source: Box::new(err) }))
}
};

self.attach_handler(IotaDID::METHOD.to_owned(), handler);
}
}
}

Expand All @@ -301,3 +355,63 @@ where
.finish()
}
}

#[cfg(test)]
mod tests {
use identity_iota_core::block::output::AliasId;
use identity_iota_core::block::output::AliasOutput;
use identity_iota_core::block::output::OutputId;
use identity_iota_core::block::protocol::ProtocolParameters;
use identity_iota_core::IotaDID;
use identity_iota_core::IotaDocument;
use identity_iota_core::IotaIdentityClient;
use identity_iota_core::IotaIdentityClientExt;

use super::*;

struct DummyClient(IotaDocument);

#[async_trait::async_trait]
impl IotaIdentityClient for DummyClient {
async fn get_alias_output(&self, _id: AliasId) -> identity_iota_core::Result<(OutputId, AliasOutput)> {
unreachable!()
}
async fn get_protocol_parameters(&self) -> identity_iota_core::Result<ProtocolParameters> {
unreachable!()
}
}

#[async_trait::async_trait]
impl IotaIdentityClientExt for DummyClient {
async fn resolve_did(&self, did: &IotaDID) -> identity_iota_core::Result<IotaDocument> {
if self.0.id().as_str() == did.as_str() {
Ok(self.0.clone())
} else {
Err(identity_iota_core::Error::DIDResolutionError(
iota_sdk::client::error::Error::NoOutput(did.to_string()),
))
}
}
}

#[tokio::test]
async fn test_multiple_handlers() {
let did1 =
IotaDID::parse("did:iota:smr:0x0101010101010101010101010101010101010101010101010101010101010101").unwrap();
let document = IotaDocument::new_with_id(did1.clone());
let dummy_smr_client = DummyClient(document);

let did2 = IotaDID::parse("did:iota:0x0101010101010101010101010101010101010101010101010101010101010101").unwrap();
let document = IotaDocument::new_with_id(did2.clone());
let dummy_iota_client = DummyClient(document);

let mut resolver = Resolver::<IotaDocument>::new();
resolver.attach_multiple_iota_handlers(vec![("iota", dummy_iota_client), ("smr", dummy_smr_client)]);

let doc = resolver.resolve(&did1).await.unwrap();
assert_eq!(doc.id(), &did1);

let doc = resolver.resolve(&did2).await.unwrap();
assert_eq!(doc.id(), &did2);
}
}
Loading